/* Interfaces to subprocesses on VMS. Copyright (C) 1988, 1994 Free Software Foundation, Inc. This file is part of GNU Emacs. GNU Emacs is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2, or (at your option) any later version. GNU Emacs is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with GNU Emacs; see the file COPYING. If not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ /* Event flag and `select' emulation 0 is never used 1 is the terminal 23 is the timer event flag 24-31 are reserved by VMS */ #include #include #include #include #include #include "vmsproc.h" #include "lisp.h" #include "buffer.h" #include #include "process.h" #include "commands.h" #include extern Lisp_Object call_process_cleanup (); #define KEYBOARD_EVENT_FLAG 1 #define TIMER_EVENT_FLAG 23 static VMS_PROC_STUFF procList[MAX_EVENT_FLAGS+1]; get_kbd_event_flag () { /* Return the first event flag for keyboard input. */ VMS_PROC_STUFF *vs = &procList[KEYBOARD_EVENT_FLAG]; vs->busy = 1; vs->pid = 0; return (vs->eventFlag); } get_timer_event_flag () { /* Return the last event flag for use by timeouts */ VMS_PROC_STUFF *vs = &procList[TIMER_EVENT_FLAG]; vs->busy = 1; vs->pid = 0; return (vs->eventFlag); } VMS_PROC_STUFF * get_vms_process_stuff () { /* Return a process_stuff structure We use 1-23 as our event flags to simplify implementing a VMS `select' call. */ int i; VMS_PROC_STUFF *vs; for (i=1, vs = procList; ibusy) { vs->busy = 1; vs->inputChan = 0; vs->pid = 0; sys$clref (vs->eventFlag); return (vs); } } return ((VMS_PROC_STUFF *)0); } give_back_vms_process_stuff (vs) VMS_PROC_STUFF *vs; { /* Return an event flag to our pool */ vs->busy = 0; vs->inputChan = 0; vs->pid = 0; } VMS_PROC_STUFF * get_vms_process_pointer (pid) int pid; { /* Given a pid, return the VMS_STUFF pointer */ int i; VMS_PROC_STUFF *vs; /* Don't search the last one */ for (i=0, vs=procList; ibusy && vs->pid == pid) return (vs); } return ((VMS_PROC_STUFF *)0); } start_vms_process_read (vs) VMS_PROC_STUFF *vs; { /* Start an asynchronous read on a VMS process We will catch up with the output sooner or later */ int status; int ProcAst (); status = sys$qio (vs->eventFlag, vs->outputChan, IO$_READVBLK, vs->iosb, 0, vs, vs->inputBuffer, sizeof (vs->inputBuffer), 0, 0, 0, 0); if (status != SS$_NORMAL) return (0); else return (1); } extern int waiting_for_ast; /* in sysdep.c */ extern int timer_ef; extern int input_ef; select (nDesc, rdsc, wdsc, edsc, timeOut) int nDesc; int *rdsc; int *wdsc; int *edsc; int *timeOut; { /* Emulate a select call We know that we only use event flags 1-23 timeout == 100000 & bit 0 set means wait on keyboard input until something shows up. If timeout == 0, we just read the event flags and return what we find. */ int nfds = 0; int status; int time[2]; int delta = -10000000; int zero = 0; int timeout = *timeOut; unsigned long mask, readMask, waitMask; if (rdsc) readMask = *rdsc << 1; /* Unix mask is shifted over 1 */ else readMask = 0; /* Must be a wait call */ sys$clref (KEYBOARD_EVENT_FLAG); sys$setast (0); /* Block interrupts */ sys$readef (KEYBOARD_EVENT_FLAG, &mask); /* See what is set */ mask &= readMask; /* Just examine what we need */ if (mask == 0) { /* Nothing set, we must wait */ if (timeout != 0) { /* Not just inspecting... */ if (!(timeout == 100000 && readMask == (1 << KEYBOARD_EVENT_FLAG))) { lib$emul (&timeout, &delta, &zero, time); sys$setimr (TIMER_EVENT_FLAG, time, 0, 1); waitMask = readMask | (1 << TIMER_EVENT_FLAG); } else waitMask = readMask; if (waitMask & (1 << KEYBOARD_EVENT_FLAG)) { sys$clref (KEYBOARD_EVENT_FLAG); waiting_for_ast = 1; /* Only if reading from 0 */ } sys$setast (1); sys$wflor (KEYBOARD_EVENT_FLAG, waitMask); sys$cantim (1, 0); sys$readef (KEYBOARD_EVENT_FLAG, &mask); if (readMask & (1 << KEYBOARD_EVENT_FLAG)) waiting_for_ast = 0; } } sys$setast (1); /* Count number of descriptors that are ready */ mask &= readMask; if (rdsc) *rdsc = (mask >> 1); /* Back to Unix format */ for (nfds = 0; mask; mask >>= 1) { if (mask & 1) nfds++; } return (nfds); } #define MAX_BUFF 1024 write_to_vms_process (vs, buf, len) VMS_PROC_STUFF *vs; char *buf; int len; { /* Write something to a VMS process. We have to map newlines to carriage returns for VMS. */ char ourBuff[MAX_BUFF]; short iosb[4]; int status; int in, out; while (len > 0) { out = map_nl_to_cr (buf, ourBuff, len, MAX_BUFF); status = sys$qiow (0, vs->inputChan, IO$_WRITEVBLK|IO$M_NOFORMAT, iosb, 0, 0, ourBuff, out, 0, 0, 0, 0); if (status != SS$_NORMAL || (status = iosb[0]) != SS$_NORMAL) { error ("Could not write to subprocess: %x", status); return (0); } len -= out; } return (1); } static map_nl_to_cr (in, out, maxIn, maxOut) char *in; char *out; int maxIn; int maxOut; { /* Copy `in' to `out' remapping `\n' to `\r' */ int c; int o; for (o=0; maxIn-- > 0 && o < maxOut; o++) { c = *in++; *out++ = (c == '\n') ? '\r' : c; } return (o); } clean_vms_buffer (buf, len) char *buf; int len; { /* Sanitize output from a VMS subprocess Strip CR's and NULLs */ char *oBuf = buf; char c; int l = 0; while (len-- > 0) { c = *buf++; if (c == '\r' || c == '\0') ; else { *oBuf++ = c; l++; } } return (l); } /* For the CMU PTY driver */ #define PTYNAME "PYA0:" get_pty_channel (inDevName, outDevName, inChannel, outChannel) char *inDevName; char *outDevName; int *inChannel; int *outChannel; { int PartnerUnitNumber; int status; struct { int l; char *a; } d; struct { short BufLen; short ItemCode; int *BufAddress; int *ItemLength; } g[2]; d.l = strlen (PTYNAME); d.a = PTYNAME; *inChannel = 0; /* Should be `short' on VMS */ *outChannel = 0; *inDevName = *outDevName = '\0'; status = sys$assign (&d, inChannel, 0, 0); if (status == SS$_NORMAL) { *outChannel = *inChannel; g[0].BufLen = sizeof (PartnerUnitNumber); g[0].ItemCode = DVI$_UNIT; g[0].BufAddress = &PartnerUnitNumber; g[0].ItemLength = (int *)0; g[1].BufLen = g[1].ItemCode = 0; status = sys$getdviw (0, *inChannel, 0, &g, 0, 0, 0, 0); if (status == SS$_NORMAL) { sprintf (inDevName, "_TPA%d:", PartnerUnitNumber); strcpy (outDevName, inDevName); } } return (status); } VMSgetwd (buf) char *buf; { /* Return the current directory */ char curdir[256]; char *getenv (); char *s; short len; int status; struct { int l; char *a; } d; s = getenv ("SYS$DISK"); if (s) strcpy (buf, s); else *buf = '\0'; d.l = 255; d.a = curdir; status = sys$setddir (0, &len, &d); if (status & 1) { curdir[len] = '\0'; strcat (buf, curdir); } } static call_process_ast (vs) VMS_PROC_STUFF *vs; { sys$setef (vs->eventFlag); } void child_setup (in, out, err, new_argv, env) int in, out, err; register char **new_argv; char **env; { /* ??? I suspect that maybe this shouldn't be done on VMS. */ #ifdef subprocesses /* Close Emacs's descriptors that this process should not have. */ close_process_descs (); #endif if (STRINGP (current_buffer->directory)) chdir (XSTRING (current_buffer->directory)->data); } DEFUN ("call-process", Fcall_process, Scall_process, 1, MANY, 0, "Call PROGRAM synchronously in a separate process.\n\ Program's input comes from file INFILE (nil means null device, `NLA0:').\n\ Insert output in BUFFER before point; t means current buffer;\n\ nil for BUFFER means discard it; 0 means discard and don't wait.\n\ Fourth arg DISPLAY non-nil means redisplay buffer as output is inserted.\n\ Remaining arguments are strings passed as command arguments to PROGRAM.\n\ This function waits for PROGRAM to terminate, unless BUFFER is 0;\n\ if you quit, the process is killed.") (nargs, args) int nargs; register Lisp_Object *args; { Lisp_Object display, buffer, path; char oldDir[512]; int inchannel, outchannel; int len; int call_process_ast (); struct { int l; char *a; } dcmd, din, dout; char inDevName[65]; char outDevName[65]; short iosb[4]; int status; int SpawnFlags = CLI$M_NOWAIT; VMS_PROC_STUFF *vs; VMS_PROC_STUFF *get_vms_process_stuff (); int fd[2]; int filefd; register int pid; char buf[1024]; int count = specpdl_ptr - specpdl; register unsigned char **new_argv; struct buffer *old = current_buffer; CHECK_STRING (args[0], 0); if (nargs <= 1 || NILP (args[1])) args[1] = build_string ("NLA0:"); else args[1] = Fexpand_file_name (args[1], current_buffer->directory); CHECK_STRING (args[1], 1); { register Lisp_Object tem; buffer = tem = args[2]; if (nargs <= 2) buffer = Qnil; else if (!(EQ (tem, Qnil) || EQ (tem, Qt) || XFASTINT (tem) == 0)) { buffer = Fget_buffer (tem); CHECK_BUFFER (buffer, 2); } } display = nargs >= 3 ? args[3] : Qnil; { /* if (args[0] == "*dcl*" then we need to skip pas the "-c", else args[0] is the program to run. */ register int i; int arg0; int firstArg; if (strcmp (XSTRING (args[0])->data, "*dcl*") == 0) { arg0 = 5; firstArg = 6; } else { arg0 = 0; firstArg = 4; } len = XSTRING (args[arg0])->size + 1; for (i = firstArg; i < nargs; i++) { CHECK_STRING (args[i], i); len += XSTRING (args[i])->size + 1; } new_argv = alloca (len); strcpy (new_argv, XSTRING (args[arg0])->data); for (i = firstArg; i < nargs; i++) { strcat (new_argv, " "); strcat (new_argv, XSTRING (args[i])->data); } dcmd.l = len-1; dcmd.a = new_argv; status = get_pty_channel (inDevName, outDevName, &inchannel, &outchannel); if (!(status & 1)) error ("Error getting PTY channel: %x", status); if (INTEGERP (buffer)) { dout.l = strlen ("NLA0:"); dout.a = "NLA0:"; } else { dout.l = strlen (outDevName); dout.a = outDevName; } vs = get_vms_process_stuff (); if (!vs) { sys$dassgn (inchannel); sys$dassgn (outchannel); error ("Too many VMS processes"); } vs->inputChan = inchannel; vs->outputChan = outchannel; } filefd = open (XSTRING (args[1])->data, O_RDONLY, 0); if (filefd < 0) { sys$dassgn (inchannel); sys$dassgn (outchannel); give_back_vms_process_stuff (vs); report_file_error ("Opening process input file", Fcons (args[1], Qnil)); } else close (filefd); din.l = XSTRING (args[1])->size; din.a = XSTRING (args[1])->data; /* Start a read on the process channel */ if (!INTEGERP (buffer)) { start_vms_process_read (vs); SpawnFlags = CLI$M_NOWAIT; } else SpawnFlags = 0; /* On VMS we need to change the current directory of the parent process before forking so that the child inherit that directory. We remember where we were before changing. */ VMSgetwd (oldDir); child_setup (0, 0, 0, 0, 0); status = lib$spawn (&dcmd, &din, &dout, &SpawnFlags, 0, &vs->pid, &vs->exitStatus, 0, call_process_ast, vs); chdir (oldDir); if (status != SS$_NORMAL) { sys$dassgn (inchannel); sys$dassgn (outchannel); give_back_vms_process_stuff (vs); error ("Error calling LIB$SPAWN: %x", status); } pid = vs->pid; if (INTEGERP (buffer)) { #ifndef subprocesses wait_without_blocking (); #endif subprocesses return Qnil; } if (!NILP (display) && INTERACTIVE) prepare_menu_bars (); record_unwind_protect (call_process_cleanup, Fcons (make_number (fd[0]), make_number (pid))); if (BUFFERP (buffer)) Fset_buffer (buffer); immediate_quit = 1; QUIT; while (1) { sys$waitfr (vs->eventFlag); if (vs->iosb[0] & 1) { immediate_quit = 0; if (!NILP (buffer)) { vs->iosb[1] = clean_vms_buffer (vs->inputBuffer, vs->iosb[1]); InsCStr (vs->inputBuffer, vs->iosb[1]); } if (!NILP (display) && INTERACTIVE) redisplay_preserve_echo_area (); immediate_quit = 1; QUIT; if (!start_vms_process_read (vs)) break; /* The other side went away */ } else break; } sys$dassgn (inchannel); sys$dassgn (outchannel); give_back_vms_process_stuff (vs); /* Wait for it to terminate, unless it already has. */ wait_for_termination (pid); immediate_quit = 0; set_current_buffer (old); return unbind_to (count, Qnil); } create_process (process, new_argv) Lisp_Object process; char *new_argv; { int pid, inchannel, outchannel, forkin, forkout; char old_dir[512]; char in_dev_name[65]; char out_dev_name[65]; short iosb[4]; int status; int spawn_flags = CLI$M_NOWAIT; int child_sig (); struct { int l; char *a; } din, dout, dprompt, dcmd; VMS_PROC_STUFF *vs; VMS_PROC_STUFF *get_vms_process_stuff (); status = get_pty_channel (in_dev_name, out_dev_name, &inchannel, &outchannel); if (!(status & 1)) { remove_process (process); error ("Error getting PTY channel: %x", status); } dout.l = strlen (out_dev_name); dout.a = out_dev_name; dprompt.l = strlen (DCL_PROMPT); dprompt.a = DCL_PROMPT; if (strcmp (new_argv, "*dcl*") == 0) { din.l = strlen (in_dev_name); din.a = in_dev_name; dcmd.l = 0; dcmd.a = (char *)0; } else { din.l = strlen ("NLA0:"); din.a = "NLA0:"; dcmd.l = strlen (new_argv); dcmd.a = new_argv; } /* Delay interrupts until we have a chance to store the new fork's pid in its process structure */ sys$setast (0); vs = get_vms_process_stuff (); if (vs == 0) { sys$setast (1); remove_process (process); error ("Too many VMS processes"); } vs->inputChan = inchannel; vs->outputChan = outchannel; /* Start a read on the process channel */ start_vms_process_read (vs); /* Switch current directory so that the child inherits it. */ VMSgetwd (old_dir); child_setup (0, 0, 0, 0, 0); status = lib$spawn (&dcmd, &din, &dout, &spawn_flags, 0, &vs->pid, &vs->exitStatus, 0, child_sig, vs, &dprompt); chdir (old_dir); if (status != SS$_NORMAL) { sys$setast (1); remove_process (process); error ("Error calling LIB$SPAWN: %x", status); } vs->pid &= 0xffff; /* It needs to fit in a FASTINT, we don't need the rest of the bits */ pid = vs->pid; /* ON VMS process->infd holds the (event flag-1) that we use for doing I/O on that process. `input_wait_mask' is the cluster of event flags we can wait on. Event flags returned start at 1 for the keyboard. Since Unix expects descriptor 0 for the keyboard, we subtract one from the event flag. */ inchannel = vs->eventFlag-1; /* Record this as an active process, with its channels. As a result, child_setup will close Emacs's side of the pipes. */ chan_process[inchannel] = process; XSETFASTINT (XPROCESS (process)->infd, inchannel); XSETFASTINT (XPROCESS (process)->outfd, outchannel); XPROCESS (process)->status = Qrun /* Delay interrupts until we have a chance to store the new fork's pid in its process structure */ #define NO_ECHO "set term/noecho\r" sys$setast (0); /* Send a command to the process to not echo input The CMU PTY driver does not support SETMODEs. */ write_to_vms_process (vs, NO_ECHO, strlen (NO_ECHO)); XSETFASTINT (XPROCESS (process)->pid, pid); sys$setast (1); } child_sig (vs) VMS_PROC_STUFF *vs; { register int pid; Lisp_Object tail, proc; register struct Lisp_Process *p; int old_errno = errno; pid = vs->pid; sys$setef (vs->eventFlag); for (tail = Vprocess_alist; XSYMBOL (tail) != XSYMBOL (Qnil); tail = XCONS (tail)->cdr) { proc = XCONS (XCONS (tail)->car)->cdr; p = XPROCESS (proc); if (EQ (p->childp, Qt) && XFASTINT (p->pid) == pid) break; } if (XSYMBOL (tail) == XSYMBOL (Qnil)) return; p->status = Fcons (Qexit, Fcons (make_number (vs->exitStatus), Qnil)) } syms_of_vmsproc () { defsubr (&Scall_process); } init_vmsproc () { char *malloc (); int i; VMS_PROC_STUFF *vs; for (vs=procList, i=0; ibusy = 0; vs->eventFlag = i; sys$clref (i); vs->inputChan = 0; vs->pid = 0; } procList[0].busy = 1; /* Zero is reserved */ }