/************************************************************************ * This program is Copyright (C) 1986 by Jonathan Payne. JOVE is * * provided to you without charge, and with no warranty. You may give * * away copies of JOVE, including sources, provided that this notice is * * included in all the files. * ************************************************************************/ /* * Modified: October 1988, by Tom Hageman [TRH] * add support for hybrid System V/BSD systems without job control. * [22-Aug-91] add VMS support. */ #include "tune.h" #ifdef IPROCS /* the rest of this file */ RCS("$Id: iproc.c,v 14.31.0.12 1994/06/23 02:54:50 tom Exp tom $") #include "jove.h" #include "ctype.h" #include "io.h" #include "process.h" #include "re.h" #include "tty.h" #define DEAD 0 /* Dead but haven't informed user yet */ #define STOPPED 1 /* Job stopped */ #define RUNNING 2 /* Just running */ #define NEW 3 /* This process is brand new */ /* If process is dead, flags says how. */ #define EXITED 1 #define KILLED 2 #define proc_buf(p) ((p)->p_buffer->b_name) #define proc_cmd(p) ((p)->p_name) #define proc_state(p) ((p)->p_state) #define proc_note(p,s) (updmodline(), (p)->p_state = (s)) #define isdead(p) ((p) == NULL || proc_state(p) == DEAD || (p)->p_fd < 0) private Process *procs ZERO; int NumProcs ZERO; private void free_proc __(( Process *_(p) )), proc_kill __(( Process *_(p), int _(sig) )), proc_rec __(( Process *_(p), const char *_(buf) )); private Process * new_proc __(( const char *_(bufname), int _(clobber), va_list _(ap) )); private void unzero __(( char *_(str), int _(n) )); #ifdef PIPEPROCS # include "ipr-pipe.ci" #else # if !vms # include "ipr-pty.ci" # else # include "ipr-vms.ci" # endif #endif DEF_STR( "process-prompt", proc_prompt, 128, V_REGEXP ) _IF(def IPROCS) = "% "; _IF(def PRIVATE) public const char * pstate(p) register Process *p; { static char resultbuf[30]; register char *result = resultbuf; if (p == NULL) return "No Process"; switch (proc_state(p)) { case NEW: result = "DBX New"; break; case STOPPED: result = "DBX Stopped"; break; case RUNNING: result = "DBX Running"; break; case DEAD: sprintf(result, (p->p_howdied == KILLED) ? "Killed %d" : (p->p_reason == EXIT_SUCCESS) ? "Done" : "Exit %d", p->p_reason); return result; default: return "Unknown state"; } if (!p->p_dbx_mode) result += 4; /* skip "DBX " */ return result; } void KillProcs() { register Process *p; register int asked = NO; if (p = procs) do { if (isdead(p)) continue; if (!asked && !(asked = yes_or_no_p('Y', "Should I kill your i-processes? "))) return; proc_kill(p, SIGKILL); } while (p = p->p_next); } int pbufalivep(b) Buffer *b; { register Process *p; return (b && (p = b->b_process, !isdead(p))); } void pbuftiedp(b) register Buffer *b; { register Process *p = b->b_process; if (!isdead(p)) complain("Process %s, attached to %b, is %s.", proc_cmd(p), b, pstate(p)); } DEF_STR( "dbx-format-string", dbx_parse_fmt, 128, V_REGEXP ) _IF(def IPROCS)_IF(def PRIVATE) = "line \\([0-9]*\\) in \\{file,\\} *\"\\([^\"]*\\)\"\ \\| at \\([^ ]+\\):\\([0-9]+\\)$\\|^\\{0x[0-9a-fA-F]+\t,\\}\\([0-9]+\\)[^:]"; /* dbx syntax\|Gnu Debugger syntax. */ DEF_STR( "dbx-program-pattern", dbx_programs, 128, V_REGEXP ) _IF(def IPROCS)_IF(def PRIVATE) = "dbx\\|gdb"; DEF_CMD( "process-dbx-output", DBXpoutput, NO ) _IF(def IPROCS) { register Process *p; if ((p = curbuf->b_process) == NULL) complain("[Must be in a process buffer to enable dbx mode]"); p->p_dbx_mode ^= 1; /* toggle */ updmodline(); } private void watch_input __(( Mark *_(m) )); private void watch_input(m) Mark *m; { Bufpos save; Bufpos *next; DOTsave(&save); ToMark(m); while ((next = dosearch(dbx_parse_fmt, FORWARD, YES)) != NULL) { char fnamebuf[FILESIZE]; register char *fname = fnamebuf; register int lnum = get_FL_info(fname); static Buffer *b ZERO; register Window *savew = curwind; SetDot(next); if (fname[0]) b = do_find((Window *) 0, fname, YES); if (lnum <= 0 || b == NULL) continue; pop_wind(b->b_name, NO, NO); /* [TRH] although do_find() is now capable of skipping to a given line number in the buffer, we cannot use this here because pop_wind() positions to the *window*s idea of point if the buffer is already visible in that window. So we have to do it ourselves... */ to_line(lnum); #if (HIGHLIGHT) WHighLight(curwind, b->b_dot); #endif SetWind(savew); } SetDot(&save); } /* Process receive: receives the characters in buf, and appends them to the buffer associated with p. */ private void proc_rec(p, buf) register Process *p; const char *buf; { Buffer *saveb = curbuf; register Window *w = curwind; register Buffer *pb = p->p_buffer; register Mark *savepoint; int /*- sameplace = NO, -*/ do_disp = NO; /* display only if the point where we left off is visible in a window */ if (w->w_bufp == pb || (w = windbp(pb))) if (in_window(w, p->p_mark->m_line) >= 0) do_disp++; SetBuf(pb); savepoint = MakeMark(pb->b_dot, pb->b_char, FLOATER); ToMark(p->p_mark); /* Where output last stopped. */ #if NO /* [TRH] this is obsolete now FLOATERing marks float along with Point */ if (savepoint->m_line == pb->b_dot && savepoint->m_char == pb->b_char) sameplace++; #endif #ifdef PROC_WRAP buf = chop((char *)buf, -1); do { ins_str(buf, YES); } while ((buf = chop((char *)0, 0)) && (LineInsert(1), TRUE)); #else ins_str(buf, YES); #endif if (do_disp && p->p_dbx_mode) watch_input(p->p_mark); /* output from the process should be buffered for this to work. So PIPEPROCS usually work OK, but for instance CDB through pty fails (on hp-ux 5.5). */ MarkSet(p->p_mark, pb->b_dot, pb->b_char); #if NO if (!sameplace) #endif ToMark(savepoint); /* Back to where we were. */ DelMark(savepoint); /* redisplay now, instead of right after the ins_str, so that we don't get a bouncing effect if point is not the same as the process output position */ if (do_disp) { w->w_line = pb->b_dot; w->w_char = 0; /* so long lines won't bounce if this is not the active window. */ if (inIOread) { /* don't disturb Typeout or similar */ inIOread--; /* so charp() will work */ redisplay(); inIOread++; } } SetBuf(saveb); } private void proc_kill(p, sig) register Process *p; { if ((p) == NULL || proc_state(p) == DEAD) return; if (p->p_pid <= 0 || killpg(p->p_pid, sig) < 0) s_mess("Cannot kill %b!", p->p_buffer); } /* Create a Process descriptor, and bring it up in a window */ private Process * new_proc(bufname, clobber, ap) const char *bufname; va_register va_list ap; { register Process *newp = (Process *) emalloc(sizeof(Process)); register Buffer *newbuf; newp->p_next = procs; procs = newp; newp->p_fd = -1; /* no I/O channel yet; so looks dead */ newp->p_state = NEW; newp->p_reason = 0; #ifdef IPARSE newp->p_terminate = NULL; #endif { /* build command line */ register char *cp, *d = genbuf; /* don't show "/bin/sh -c" (or whatever is in Shell and ShFlags) */ if ((cp = va_arg(ap, char *)) == Shell) { if ((cp = va_arg(ap, char *)) == ShFlags) cp = va_arg(ap, char *); else d = appcpy(d, Shell), *d++ = ' '; } /* Automagically enable DBX mode, based on program name. */ newp->p_dbx_mode = LookingAt(dbx_programs, basename(cp), 0); while ((d = appcpy(d, cp)), (cp = va_arg(ap, char *))) *d++ = ' '; } newp->p_name = copystr(genbuf); newbuf = do_select((Window *) 0, bufname); SETBUFTYPE(newbuf, B_IPROCESS); #ifdef FIXED_MAPS /* This kludgery is needed since SetBuf won't PushPB when new i-process buffer is the same as curbuf. Argh! */ if (newbuf == curbuf && newbuf->b_process == NULL) PushPBs(); #endif newp->p_buffer = newbuf; newbuf->b_process = newp; /* sorta circular, eh? */ pop_wind(bufname, clobber, B_IPROCESS); /* Pop_wind() after everything is set up; important! Bindings won't work right unless newbuf->b_process is already set up BEFORE NEWBUF is first SetBuf()'d. */ ToLast(); if (!bolp(newbuf)) LineInsert(1); /* * [TRH] !!this mark should NOT float around with Point, so use * "semi"-floatering mode... */ newp->p_mark = MakeMark(newbuf->b_dot, newbuf->b_char, 1); NumProcs++; return newp; } /* Deal with a process' death. does all the necessary cleanups */ private void free_proc(child) register Process *child; { register Process **hp; register Buffer *cb = curbuf; if (!isdead(child)) return; for (hp = &procs; (*hp) != child; hp = &(*hp)->p_next) if ((*hp) == NULL) { f_mess("?process?"); return; } (*hp) = child->p_next; proc_close(child); /* if not already closed */ /* It's possible that the buffer has been given another process between the time CHILD dies and CHILD's death is noticed (via list-processes). So we only set it the buffer's process to NULL if CHILD is still the controlling process. */ if (child->p_buffer->b_process == child) { #ifdef FIXED_MAPS if (child->p_buffer == cb) PopPBs(); /* not a process anymore */ #endif child->p_buffer->b_process = NULL; } if ((curbuf = child->p_buffer) != NULL && child->p_mark != NULL) DelMark(child->p_mark); curbuf = cb; free((void_*) child->p_name); free((void_*) child); } DEF_CMD( "kill-process", ProcKill, NO ) _IF(def IPROCS) { register const Buffer *b = curbuf; if (b->b_process == NULL) { /* choose a suitable default */ if (procs == NULL) complain("[No subprocesses]"); b = procs->p_buffer; } if ((b = buf_exists(ask_buf(b))) == NULL) complain("[No such buffer]"); if (b->b_process == NULL) complain("[%b not tied to a process]", b); proc_kill(b->b_process, SIGKILL); } DEF_CMD( "list-processes", ProcList, NO ) _IF(def IPROCS) { register Process *p, *next; register const char *fmt = "%-15s %-15s %-8s %s"; if ((p = procs) == NULL) { message("[No subprocesses]"); return; } TOstart("*Process list*", TRUE); Typeout(fmt, "Buffer", "Status", "Pid", "Command"); Typeout(fmt, "------", "------", "---", "-------"); do { Typeout(fmt, proc_buf(p), pstate(p), itos(p->p_pid), p->p_name); next = p->p_next; if (isdead(p)) { free_proc(p); updmodline(); } } while (p = next); TOstop(); } #ifdef CHDIR DEF_INT( "shell-cwd-sync", CwdSync, V_BOOL ) _IF(def IPROCS)_IF(def CHDIR)_IF(def PRIVATE) ZERO; private void catch_cwd(from) Mark *from; { register Bufpos *bp; ToMark(from); while (bp = dosearch("\\<\\(\\{cd,pushd,popd,dirs,pwd\\}\\)\\>[ \t]*\\([^ \t;]*\\)", FORWARD, YES)) { char cmdbuf[10], dirbuf[FILESIZE]; int matchlen = REbom - REeom; /* sic! */ putmatch(1, cmdbuf, sizeof cmdbuf); putmatch(2, dirbuf, sizeof dirbuf); Eof(); /* in case the command fails */ if (dirbuf[0] == '\0' && cmdbuf[0] == 'c' /* cd */) strcpy(dirbuf, HomeDir); /* check that JOVE can chdir to it. */ if (dirbuf[0] != '\0') { char tmpdir[FILESIZE]; env_expand(strcpy(tmpdir, dirbuf)); if (chdir(PathParse(tmpdir, genbuf)) != 0) { rbell(); s_mess("[JOVE cannot \"%s %s\"; possibly out of sync with shell]", cmdbuf, dirbuf); break; } } /* execute the command in JOVE */ sprintf(Inputp = genbuf, "%s %s\n", cmdbuf, dirbuf); Extend(); Inputp = NULL; if (cmdbuf[1] == 'i' /* dirs */ || cmdbuf[1] == 'w' /* pwd */) continue; /* replace the match with an explicit cd to the directory, so shells that don't have pushd/popd also benefit. */ SetDot(bp); exp = matchlen, exp_p = NO, DelNChar(); # if !vms ins_str(sprint("cd %s", pwd()), NO); # else ins_str(sprint("set default %s", ux2vms(genbuf, pwd())), NO); # endif } Eof(); } #endif /* CHDIR */ private void do_rtp __(( Mark *_(mp) )); private void do_rtp(mp) register Mark *mp; { register Buffer *cb = curbuf; register Process *p = cb->b_process; register Line *line; register int length; Line *line1 = cb->b_dot, *line2 = mp->m_line; int char1 = cb->b_char, char2 = mp->m_char; if (isdead(p) || p->p_buffer != cb) return; fixorder(&line1, &char1, &line2, &char2); line = line1; do { length = ltobuf(line, genbuf); if (line == line2) length = char2; else genbuf[length++] = '\n'; proc_write(p, genbuf + char1, length - char1); char1 = 0; } while (line != line2 && (line = line->l_next)); } DEF_CMD( "process-newline", SendData, ARG(YES) ) _IF(def IPROCS); DEF_CMD( "process-send-data-no-return", SendData, ARG(NO) ) _IF(def IPROCS) { register Buffer *cb = curbuf; register Process *p = cb->b_process; if (isdead(p)) return; #ifdef ABBREV if (BufMinorMode(cb, Abbrev)) AbbrevExpand(); #endif if (lastp(cb, cb->b_dot)) { Eol(); if (LastCmd->Type & ARG(YES)) LineInsert(1); #ifdef CHDIR if (True(CwdSync) && cb->b_name[0] == '*' /* "*shell*" */) /* all other i-process buffer names start with '[' */ catch_cwd(p->p_mark); #endif do_rtp(p->p_mark); MarkSet(p->p_mark, cb->b_dot, cb->b_char); } else { register int match = 0; /* Either we're looking at a prompt, or we're not, in which case we want to strip off the beginning of the line anything that looks like what the prompt at the end of the file is. In other words, if "(dbx) stop in ProcessNewline" is the line we're on, and the last line in the buffer is "(dbx) ", then we strip off the leading "(dbx) " from this line, because we know it's part of the prompt. But this only happens if "(dbx) " isn't one of the process prompts ... follow what I'm saying? */ strcpy(genbuf, linebuf); Eof(); while (LookingAt(proc_prompt, genbuf, match) && REeom > match) match = REeom; if (!match) match = numcomp(genbuf, linebuf); ins_str(genbuf + match, NO); } } DEF_CMD( "shell", ShellProc, NO ) _IF(def IPROCS) { register const char *shbuf = "*shell*"; register const Buffer *b; register Process *p; register const char *shell; if ((shell = IShell) == NULL) shell = Shell; if ((b = buf_exists(shbuf)) == NULL || (p = b->b_process, isdead(p))) proc_strt(shbuf, NO, shell, IShFlags, (char *) 0); pop_wind(shbuf, NO, NO); } DEF_CMD( "i-shell-command", Iprocess, NO ) _IF(def IPROCS) { char bnamebuf[BNAMESIZE]; register Buffer *b; register Process *p; register char *command, *bname, *basenm; register int try = 0; Window *owind = curwind; command = set_str(&ShcomBuf, ask(ShcomBuf, ProcFmt)); bname = basenm = MakeName(command); while ((b = buf_exists(bname)) && (p = b->b_process, !isdead(p))) sprintf(bname = bnamebuf, "%s'%d", basenm, ++try); proc_strt(bname, (exp_p == NO), Shell, ShFlags, command, (char *) 0); SetWind(owind); } #ifdef IPARSE /* Run `command' as i-process in buffer `bname' with `term_handler' to be invoked when the command terminates. */ void IprocWTerm(bname, command, term_handler) register const char *bname; const char *command; data_obj *term_handler; { register Buffer *b; register Process *p; char namebuf[BNAMESIZE]; register int try = 0, asked = NO; register char *tryname = (char *) bname; /* Find any running sub-processes that apparently belong to this family. If any is found, ask for confirmation before creating a parallel session. After asking, break the loop as soon as a "dead" buffer is encountered. */ while (b = buf_exists(tryname)) { if (p = b->b_process, isdead(p)) { if (asked) break; } else if (!asked) { confirm("%s is still running. Shall I create a parallel session? ", bname); asked++; } sprintf(tryname = namebuf, "%s'%d", bname, ++try); } if (asked) bname = tryname; proc_strt(bname, YES, Shell, ShFlags, command, (char *) 0); /* Make sure that the process was indeed created. */ if ((b = buf_exists(bname)) && (p = b->b_process, !isdead(p))) { set_wsize(EWSize); p->p_terminate = term_handler; #if vms VMS_FORCE_IPROC_TERMINATION_KLUDGE(p); #endif } } #endif /* IPARSE */ void kill_off(pid, w) int pid; WAIT_T w; { register Process *child; char msgbuf[sizeof mesgbuf]; register char *msg = msgbuf; register Window *wind; if ((child = proc_pid(pid)) == NULL) return; updmodline(); /* We're changing state ... */ if (WIFSTOPPED(w)) child->p_state = STOPPED; else { if (WIFEXITED(w)) { child->p_reason = WEXITSTATUS(w); child->p_howdied = EXITED; } else if (WIFSIGNALED(w)) { child->p_reason = WTERMSIG(w); child->p_howdied = KILLED; } proc_close(child); child->p_state = DEAD; /* This order is important!! */ } sprintf(msg, "[Process %s: %s]", proc_cmd(child), pstate(child)); dobell(2); /* display on message line if not visible in a window */ if ((wind = windbp(child->p_buffer)) == NULL || in_window(wind, child->p_mark->m_line) < 0) { if (!Asking) { message(msg); errormsg++; /* Makes message linger around. */ redisplay(); } } proc_rec(child, msg); proc_rec(child, "\n"); #ifdef IPARSE { register Buffer *b; register data_obj *cp; if ((cp = child->p_terminate) && /* only if buffer non-empty (apart from what we just inserted.) */ (b = child->p_buffer, !firstp(b, b->b_last->l_prev))) { int doit; char savemesg[sizeof mesgbuf]; strcpy(savemesg, mesgbuf); /* remember old message */ doit = yes_or_no_p(NIL, "%s %s? ", msg, cp->Name); message(savemesg); if (doit) { pop_wind(b->b_name, NO, NO); exp_p = NO, exp = 1; /* just once */ ExecCmd(cp); } redisplay(); } } #endif /* IPARSE */ --NumProcs; } #ifdef FIXED_MAPS /* Obsolete with new Keymap mechnism. */ /* Push/pop process bindings. I openly acknowledge that this is a kludge, but I can't be bothered making it right. */ struct proc_bind { data_obj **pb_at; data_obj *pb_push; data_obj *pb_cmd; struct proc_bind *pb_next; }; private struct proc_bind *PBinds ZERO; void PopPBs() { register struct proc_bind *p; if (p = PBinds) do { p->pb_cmd = *p->pb_at; *p->pb_at = p->pb_push; } while (p = p->pb_next); } void PushPBs() { register struct proc_bind *p; if (p = PBinds) do { p->pb_push = *p->pb_at; *p->pb_at = p->pb_cmd; } while (p = p->pb_next); } DEF_CMD( "process-bind-to-key", ProcBind, NO ) _IF(def IPROCS)_IF(def FIXED_MAPS) { register data_obj *d, **at; register struct proc_bind *p; d = findcom(ProcFmt); s_mess(ProcArgFmt, d->Name); at = MapKey(activemap, MAP_BIND); if (p = PBinds) do { if (p->pb_at == at) break; } while (p = p->pb_next); if (p == NULL) { p = (struct proc_bind *) emalloc(sizeof *p); p->pb_next = PBinds; p->pb_push = *at; PBinds = p; } p->pb_at = at; p->pb_cmd = d; if (curbuf->b_process) *at = d; } #endif /* FIXED_MAPS */ /* Strip null-bytes from string, and zero-terminate. */ private void unzero(str, n) char *str; { register char *s = str, *d, *end = s + n; while (*s++) { if (s >= end) { *s = '\0'; #ifdef CRLF goto do_crlf; /* Yuck! but I'm lazy right now... */ #else return; #endif } } d = s - 1; while (s < end) if ((*d++ = *s++) == '\0') --d; *d = '\0'; #ifdef CRLF do_crlf: if (True(BinRdFile)) return; /* Ugly hack to convert CRLF pairs to LF. */ s = str; do { if (*s == '\0') return; } while (!(*s++ == '\r' && (*s == '\n' || *s == '\0'))); d = s - 1; while (*d = *s++) { if (*d++ == '\r' && (*s == '\n' || *s == '\0')) --d; } #endif } #endif /* IPROCS */ /*====================================================================== * $Log: iproc.c,v $ * Revision 14.31.0.12 1994/06/23 02:54:50 tom * (ProcList): fix name of scratch buffer; (Iprocess): fix uniquized bufname. * * Revision 14.31.0.10 1994/04/22 18:24:21 tom * (new_proc): use `va_register va_list'; (unzero): handle CRLF. * * Revision 14.31.0.9 1993/12/13 04:11:35 tom * (dbx-program-pattern): new user variable; (new_proc): use it; * (watch_output): fix && -> || typo. * * Revision 14.31 1993/02/15 02:01:47 tom * remove (void) casts. * * Revision 14.30 1993/02/05 00:07:29 tom * cleanup whitespace; some random optimizations. * * Revision 14.29 1992/12/30 11:05:27 tom * some changes for the benefit of incremental error parsing. * * Revision 14.28 1992/10/24 01:24:20 tom * convert to "port{ansi,defs}.h" conventions. * * Revision 14.26 1992/08/26 23:56:54 tom * fix bug in watch_input() introduced in 14.18; add GDB syntax to * default "dbx-format-string"; use explicit "i-shell", "i-shell-flags" in * "shell"; make process exit message more persistent; add RCS directives. * */