/* -*- mode: C; mode: fold; -*- */ #include "config.h" #include "jed-feat.h" #include #if JED_HAS_SUBPROCESSES /* Everything else here is in this '#if' */ /*{{{ Include Files */ #include #include #include #include #include "jdmacros.h" #ifdef HAVE_UNISTD_H # include # include #endif #include #ifdef HAVE_SYS_WAIT_H # include #endif #ifndef WEXITSTATUS # define WEXITSTATUS(stat_val) ((unsigned)(stat_val) >> 8) #endif #ifndef WIFEXITED # define WIFEXITED(stat_val) (((stat_val) & 255) == 0) #endif #ifdef REALLY_HAVE_TERMIOS_H # define USE_PTY #endif #include "buffer.h" #include "ins.h" #include "ledit.h" #include "misc.h" #include "jprocess.h" #include "paste.h" #include "sig.h" /*}}}*/ int Num_Subprocesses; int Max_Subprocess_FD; /* This also servers as a lookup table for actual system pids to the * pseudo-pids used here. See, e.g., jed_get_child_status for usage in this * fashion. */ int Subprocess_Read_fds [MAX_PROCESSES][2]; /* 0 is actual fd, 1 is our rep */ volatile int Child_Status_Changed_Flag;/* if this is non-zero, editor * should call the appropriate * function below to call slang * handlers. */ typedef struct /*{{{*/ { int flags; /* This is zero if the process is gone * and the status is nolonger avail */ #define PROCESS_RUNNING 1 #define PROCESS_STOPPED 2 #define PROCESS_ALIVE 3 #define PROCESS_EXITED 4 #define PROCESS_SIGNALLED 8 int return_status; /* This value depends on the flags */ int status_changed; /* non-zero if status changed. */ int rd, wd; /* read/write descriptors */ int is_pty; int pid; /* real process pid */ int output_type; #define PROCESS_USE_BUFFER 1 #define PROCESS_USE_SLANG 2 #define PROCESS_SAVE_POINT 4 #define PROCESS_AT_POINT 8 Buffer *buffer; /* buffer associated with process */ SLang_Name_Type *slang_fun; /* function to pass output to */ SLang_MMT_Type *umark; /* marks point of last output */ SLang_Name_Type *status_change_fun; /* call this if process status changes * The function should be declared like * define fun (pid, flags); * The flags parameter corresponds to * the flags field in this struct and * the pid is NOT the pid of this struct */ } /*}}}*/ Process_Type; static Process_Type Processes[MAX_PROCESSES]; static int signal_safe_close (int fd) { while (-1 == close (fd)) { #ifdef EINTR if (errno == EINTR) continue; #endif return -1; } return 0; } static int signal_safe_open (char *file, int mode) { int fd; while (-1 == (fd = open (file, mode))) { #ifdef EINTR if (errno == EINTR) continue; #endif #ifdef EAGAIN if (errno == EAGAIN) { sleep (1); continue; } #endif return -1; } return fd; } static int signal_safe_fcntl (int fd, int cmd, int arg) { int ret; while (-1 == (ret = fcntl (fd, cmd, arg))) { #ifdef EINTR if (errno == EINTR) continue; #endif #ifdef EAGAIN if (errno == EAGAIN) { sleep (1); continue; } #endif return -1; } return ret; } static int signal_safe_dup2 (int fd1, int fd2) { while (-1 == dup2 (fd1, fd2)) { #ifdef EINTR if (errno == EINTR) continue; #endif return -1; } return 0; } static Process_Type *get_process (int fd) /*{{{*/ { Process_Type *p; if ((fd >= 0) && (fd < MAX_PROCESSES) && (p = &Processes[fd], p->flags != 0)) return p; msg_error ("process does not exist."); return NULL; } /*}}}*/ static void call_slang_status_change_hook (Process_Type *p) /*{{{*/ { Buffer *cbuf = CBuf; if ((p->status_change_fun == NULL) || (p->buffer == NULL)) return; cbuf->locked++; switch_to_buffer (p->buffer); SLang_push_integer ((int) (p - Processes)); SLang_push_integer (p->flags); SLexecute_function (p->status_change_fun); touch_screen (); if (CBuf != cbuf) switch_to_buffer (cbuf); cbuf->locked--; } /*}}}*/ #if 1 int jed_signal_process (int *fd, int *sig) /*{{{*/ { Process_Type *p; if (NULL == (p = get_process (*fd))) return -1; kill (p->pid, *sig); return 0; } /*}}}*/ #endif static void close_rd_and_wd (Process_Type *p) /*{{{*/ { if (p->rd != -1) { signal_safe_close (p->rd); p->rd = -1; } if (p->wd != -1) { if (p->is_pty == 0) signal_safe_close (p->wd); p->wd = -1; } } /*}}}*/ /* This routine is called to clean up after the process has exited. * After getting the exit status, we call a slang hook and if the * process is dead, adjust the process arrays to delete the process. */ static void get_process_status (Process_Type *p) /*{{{*/ { int i; int fd, slfd; /* Call slang to let it know what happened. Do it first before we * really shut it down to give the hook a chance to query the state of * it before it returns. */ call_slang_status_change_hook (p); if (p->flags & PROCESS_ALIVE) return; /* Process is dead. So perform clean up. */ close_rd_and_wd (p); if (p->buffer != NULL) p->buffer->subprocess = 0; slfd = (int) (p - Processes); if (p->umark != NULL) SLang_free_mmt (p->umark); memset ((char *)p, 0, sizeof (Process_Type)); /* Adjust the array of read descriptors */ i = 0; while (i < Num_Subprocesses) { if (Subprocess_Read_fds[i][0] == slfd) break; i++; } fd = Subprocess_Read_fds [i][0]; Num_Subprocesses--; while (i < Num_Subprocesses) { Subprocess_Read_fds[i][0] = Subprocess_Read_fds[i + 1][0]; Subprocess_Read_fds[i][1] = Subprocess_Read_fds[i + 1][1]; i++; } if (Max_Subprocess_FD == fd) { i = 0; fd = -1; while (i < Num_Subprocesses) { if (Subprocess_Read_fds[i][0] > fd) fd = Subprocess_Read_fds[i][0]; i++; } Max_Subprocess_FD = fd; } } /*}}}*/ int jed_close_process (int *fd) /*{{{*/ { Process_Type *p; if (NULL == (p = get_process (*fd))) return -1; close_rd_and_wd (p); kill (-p->pid, SIGINT); /* This is probably a bad idea. It is better to check to see if it still * around and the set a flag indicating that the user wants it killed. */ /* Did we kill it? Make sure. */ kill (-p->pid, SIGKILL); if (p->buffer != NULL) p->buffer->subprocess = 0; /* This next function wraps things up --- no need to. Let handler do it. */ /* get_process_status (p); */ return 0; } /*}}}*/ void jed_kill_process (int fd) /*{{{*/ { /* This function is called when the buffer is going to be destroyed */ Processes[fd].buffer = NULL; jed_close_process (&fd); } /*}}}*/ void jed_get_child_status (void) /*{{{*/ { Process_Type *p, *pmax; Child_Status_Changed_Flag--; get_process_input (&Number_Zero); p = Processes; pmax = p + MAX_PROCESSES; while (p < pmax) { if (p->flags && p->status_changed) { p->status_changed--; get_process_status (p); } p++; } } /*}}}*/ static void child_signal_handler (int sig) /*{{{*/ { int status; int return_status; int pid; Process_Type *p, *pmax; int save_errno = errno; (void) sig; while (1) { pid = (int) waitpid (-1, &status, WNOHANG | WUNTRACED); if (pid == -1) { if (errno == ECHILD) break; continue; } if (pid == 0) break; return_status = 0; if (WIFEXITED (status)) { return_status = WEXITSTATUS (status); status = PROCESS_EXITED; } else if (WIFSIGNALED (status)) { status = PROCESS_SIGNALLED; return_status = WTERMSIG (status); } else if (WIFSTOPPED (status)) { status = PROCESS_STOPPED; } /* What else?? */ p = Processes; pmax = p + MAX_PROCESSES; while (p < pmax) { if (p->pid == pid) { p->flags = status; p->status_changed++; p->return_status = return_status; break; } p++; } } SLsignal_intr (SIGCHLD, child_signal_handler); errno = save_errno; Child_Status_Changed_Flag++; } /*}}}*/ #ifdef USE_PTY # include "pty.c" #endif static int get_master_slave_fds (int *slave_read, int *slave_write, int *master_read, int *master_write, char *slave_tty_name, int *is_pty) { #ifdef USE_PTY int master; if (-1 == pty_open_pty (&master, slave_tty_name)) return -1; *master_read = *master_write = master; *slave_read = *slave_write = -1; *is_pty = 1; return 0; #else int fds0[2], fds1[2]; if (-1 == pipe (fds0)) return -1; if (-1 == pipe (fds1)) { signal_safe_close (fds0[0]); signal_safe_close (fds0[1]); return -1; } *master_read = fds0[0]; *slave_write = fds0[1]; *master_write = fds1[1]; *slave_read = fds1[0]; *slave_tty_name = 0; *is_pty = 0 return 0; #endif } static void my_setenv (char *what, char *value) { #ifdef HAVE_SETENV (void) setenv (what, value, 1); #else # ifdef HAVE_PUTENV char buf[512]; sprintf (buf, "%s=%s", what, value); (void) putenv (buf); # endif #endif } static void my_unsetenv (char *what) { #ifdef HAVE_UNSETENV unsetenv (what); #endif } static int open_process (char *pgm, char **argv) /*{{{*/ { int val; int pd; int slave_read, slave_write, master_read, master_write; int pid, i; Process_Type *p; SLang_MMT_Type *mmt; char slave_tty_name [80]; int max_write_tries; pd = 0; while ((pd < MAX_PROCESSES) && Processes[pd].flags) pd++; if (pd == MAX_PROCESSES) return -1; p = &Processes[pd]; SLMEMSET ((char *) p, 0, sizeof (Process_Type)); if (NULL == (mmt = jed_make_user_object_mark ())) return -1; if (-1 == get_master_slave_fds (&slave_read, &slave_write, &master_read, &master_write, slave_tty_name, &p->is_pty)) { SLang_free_mmt (mmt); return -1; } SLsignal_intr (SIGCHLD, child_signal_handler); if ((pid = fork ()) < 0) { signal_safe_close (master_read); if (p->is_pty == 0) { signal_safe_close (slave_read); signal_safe_close (master_write); signal_safe_close (slave_write); } p->flags = 0; SLang_free_mmt (mmt); return -1; } p->pid = pid; /* Make the child its own process group leader. Do it here too because * we are not sure which one will run first. We have to do this because * if not, a ^G will be sent to ALL child subprocesses possibly killing * them unless they catch the signal. This call means that the INTR signal * will not be sent to any child processes sent by this fork. */ if (p->is_pty == 0) setpgid(pid, pid); if (pid == 0) { char ch; /* child code */ for (i = 0; i < 32; i++) SLsignal (i, SIG_DFL); #ifdef USE_PTY /* Call set setsid so that the child will become the session leader. * This has the side effect that we will loose the controlling * terminal. For this reason, the pty slave is opened after setsid * and then for good luck, the controlling terminal is set * via the TIOCSCTTY ioctl. */ if (p->is_pty) { if (-1 == setsid ()) fprintf (stderr, "child: setsid failed.\n"); if (-1 == (slave_read = signal_safe_open (slave_tty_name, O_RDWR))) { fprintf (stderr, "child: failed to open slave."); _exit (1); } slave_write = slave_read; # if defined(TIOCSCTTY) while ((-1 == ioctl (slave_read, TIOCSCTTY, NULL)) && (errno == EINTR)); # endif (void) pty_setup_slave_term (slave_read, 1); } #endif /* USE_PTY */ /* At this point the slave tty is in raw mode. Make sure that * the read to synchronize with the parent blocks. */ val = signal_safe_fcntl (slave_read, F_GETFL, 0); signal_safe_fcntl (slave_read, F_SETFL, val & ~O_NONBLOCK); /* Wait here for the parent to initialize its structures */ /* This will block */ while ((-1 == read (slave_read, &ch, 1)) && (errno == EINTR)); signal_safe_fcntl (slave_read, F_SETFL, val); #ifdef USE_PTY /* Put tty back into cbreak mode. */ if (p->is_pty) (void) pty_setup_slave_term (slave_read, 0); #endif if (p->is_pty == 0) { signal_safe_close (master_write); /* close write end of 0 */ signal_safe_close (master_read); /* close read end of 1 */ } if ((signal_safe_dup2(slave_read, 0) < 0) /* stdin */ || (signal_safe_dup2 (slave_write, 1) < 0) /* stdout */ || (signal_safe_dup2 (slave_write, 2) < 0)) /* stderr */ { fprintf (stderr, "dup2 failed. errno = %d\n", errno); exit (1); } my_setenv ("TERM", "unknown"); my_unsetenv ("TERMCAP"); if (execvp (pgm, argv) < 0) { fprintf (stderr, "execvp of %s failed!\r\n", pgm); exit (1); } } /* parent */ if (p->is_pty == 0) { signal_safe_close (slave_read); signal_safe_close (slave_write); } p->flags = PROCESS_RUNNING; p->rd = master_read; p->wd = master_write; Subprocess_Read_fds[Num_Subprocesses][0] = master_read; Subprocess_Read_fds[Num_Subprocesses][1] = pd; if (master_read > Max_Subprocess_FD) Max_Subprocess_FD = master_read; Num_Subprocesses += 1; val = signal_safe_fcntl (master_read, F_GETFL, 0); val |= O_NONBLOCK; signal_safe_fcntl (master_read, F_SETFL, val); CBuf->subprocess = pd + 1; /* Processing options */ p->buffer = CBuf; p->output_type = PROCESS_USE_BUFFER; p->umark = mmt; SLang_inc_mmt (mmt); /* tell slang we are keeping a copy */ /* Tell child it is ok to go. */ /* Under Solaris, the write returns -1 with errno set to EIO. * The man page is not very clear on what this means. However, I take it * to mean the the child does not have the slave end of the PTY set up * yet. So, I try again, first without sleeping, then I sleep. * * It's ugly. Anyone have any better ideas??? */ max_write_tries = 5000; while (-1 == write (master_write, "&", 1)) { #ifdef EINTR if (errno == EINTR) continue; #endif #ifdef EIO if (errno == EIO) { if (max_write_tries) { if (max_write_tries < 5) sleep (1); max_write_tries -= 1; continue; } } #endif break; /* FIXME!! */ } return pd; } /*}}}*/ /* This function is only called when we are reading characters from the * keyboard. Keyboard input has the highest priority and this is called only * if there is no input ready. */ void read_process_input (int fd) /*{{{*/ { unsigned char buf[513]; /* last byte for 0 char */ int n; Buffer *b = CBuf, *pbuf; Process_Type *p; int otype, total; /* Should never happen */ if (NULL == (p = get_process (fd))) { return; } otype = p->output_type; pbuf = p->buffer; if (pbuf != NULL) { switch_to_buffer (pbuf); pbuf->locked++; } total = 0; if (otype & PROCESS_SAVE_POINT) push_spot (); while ((n = read (p->rd, buf, 512)) > 0) { total += n; if (p->buffer == NULL) continue; if (otype & PROCESS_USE_BUFFER) { if (0 == (otype & PROCESS_AT_POINT)) eob (); ins_chars (buf, n); jed_move_user_object_mark (p->umark); } else if (otype == PROCESS_USE_SLANG) { buf[n] = 0; SLang_push_integer ((int) (p - Processes)); SLang_push_string ((char *) buf); SLexecute_function (p->slang_fun); /* function to pass output to */ } } if (otype & PROCESS_SAVE_POINT) pop_spot (); else if (otype & PROCESS_USE_BUFFER) move_window_marks (0); if (p->buffer != NULL) { if (b != CBuf) switch_to_buffer (b); pbuf->locked--; } if (total) touch_screen (); } /*}}}*/ static int write_to_process (int fd, char *buf, unsigned int len) { if (len == 0) return 0; /* FIXME!! check for write errors!! */ while (-1 == write (fd, buf, len)) { if (errno != EINTR) break; } return 0; } int jed_send_process (int *fd, char *str) /*{{{*/ { unsigned int len; Process_Type *p = get_process (*fd); if ((p == NULL) || (p->wd == -1)) return -1; len = strlen (str); return write_to_process (p->wd, str, len); } /*}}}*/ void jed_send_process_eof (int *fd) /*{{{*/ { Process_Type *p = get_process (*fd); if (p == NULL) return; if (p->wd == -1) return; if (p->is_pty) write_to_process (p->wd, "\004", 1); else { signal_safe_close (p->wd); p->wd = -1; } } /*}}}*/ void jed_set_process (int *pd, char *what, char *s) /*{{{*/ { Process_Type *p; SLang_Name_Type *f; if (NULL == (p = get_process (*pd))) return; if (!strcmp (what, "output")) { if (*s == '.') p->output_type = PROCESS_AT_POINT | PROCESS_USE_BUFFER; else if (*s == '@') p->output_type = PROCESS_SAVE_POINT | PROCESS_USE_BUFFER; else if (*s && (NULL != (f = SLang_get_function (s)))) { p->output_type = PROCESS_USE_SLANG; p->slang_fun = f; } else p->output_type = PROCESS_USE_BUFFER; } else if (!strcmp (what, "signal")) { if (*s && (NULL != (f = SLang_get_function (s)))) { p->status_change_fun = f; } } } /*}}}*/ void jed_get_process_mark (int *fd) /*{{{*/ { Process_Type *p; if (NULL == (p = get_process (*fd))) return; SLang_push_mmt (p->umark); } /*}}}*/ int jed_open_process (int *np) /*{{{*/ { int fd = -1; char *argv[502]; int n = *np; if (CBuf->subprocess) { msg_error ("There is already a process attached to this buffer."); return -1; } if ((n > 500) || (n < 0)) { msg_error ("Arguments out of range."); return -1; } n++; /* for argv0 since *np does not include * it. */ argv[n] = NULL; while (n--) { if (SLang_pop_slstring (&argv[n])) { n++; goto free_return; } } n = 0; if ((fd = open_process(argv[0], argv)) < 0) { msg_error ("Unable to open process."); } /* free up the argument strings */ free_return: while (n <= *np) { SLang_free_slstring (argv[n]); n++; } return fd; } /*}}}*/ void jed_block_child_signal (int block) /*{{{*/ { static sigset_t new_mask, old_mask; if (block) { sigemptyset (&new_mask); sigaddset (&new_mask, SIGCHLD); (void) sigprocmask (SIG_BLOCK, &new_mask, &old_mask); return; } (void) sigprocmask (SIG_SETMASK, &old_mask, NULL); } /*}}}*/ /* Jed only calls these in pairs so that this should be fine. */ FILE *jed_popen (char *cmd, char *type) /*{{{*/ { FILE *pp; jed_block_child_signal (1); pp = popen (cmd, type); if (pp == NULL) jed_block_child_signal (0); return pp; } /*}}}*/ int jed_pclose (FILE *fp) /*{{{*/ { int ret; if (fp == NULL) return -1; ret = pclose (fp); jed_block_child_signal (0); return ret; } /*}}}*/ #if 0 /* These are my versions of popen/pclose. For some reason, the popen/pclose * do not work on SunOS when there are subprocesses. I think it has * something to do with the way pclose is waiting. * See Steven's book for more information. */ #ifndef OPEN_MAX #define OPEN_MAX 256 #endif static pid_t Popen_Child_Pids[OPEN_MAX]; FILE *jed_popen(char *cmd, char *type) /*{{{*/ { int i, pfd[2], fd; pid_t pid; FILE *fp; if (((*type != 'r') && (*type != 'w')) || (*(type + 1) != 0)) { errno = EINVAL; /* required by POSIX.2 */ return(NULL); } if (pipe(pfd) < 0) return(NULL); /* errno set by pipe() or fork() */ if ((pid = fork()) < 0) return(NULL); if (pid == 0) { /* child */ if (*type == 'r') { signal_safe_close(pfd[0]); if (pfd[1] != 1) signal_safe_dup2(pfd[1], 1); signal_safe_close(pfd[1]); } else { signal_safe_close(pfd[1]); if (pfd[0] != 0) { signal_safe_dup2(pfd[0], STDIN_FILENO); signal_safe_close(pfd[0]); } } /* POSIX requires that all streams open by previous popen * be closed. */ for (i = 0; i < OPEN_MAX; i++) { if (Popen_Child_Pids[i] > 0) signal_safe_close(i); } execl("/bin/sh", "sh", "-c", cmd, (char *) 0); _exit(127); } /* parent */ if (*type == 'r') { signal_safe_close(pfd[1]); if (NULL == (fp = fdopen(pfd[0], type))) return(NULL); } else { signal_safe_close(pfd[0]); if (NULL == (fp = fdopen(pfd[1], type))) return(NULL); } fd = fileno (fp); if (fd >= OPEN_MAX) { #ifdef EMFILE errno = EMFILE; #endif fclose (fp); return NULL; } Popen_Child_Pids [fd] = pid; return(fp); } /*}}}*/ int jed_pclose(FILE *fp) /*{{{*/ { int fd, stat; pid_t pid; int ret; fd = fileno(fp); if ((fd >= OPEN_MAX) || (0 == (pid = Popen_Child_Pids[fd]))) return -1; Popen_Child_Pids [fd] = 0; if (fclose(fp) == EOF) return(-1); /* This is the part that the SunOS pclose was apparantly screwing up. */ while (pid != (ret = waitpid(pid, &stat, 0))) { if ((ret == -1) && (errno != EINTR)) return -1; } ret = WEXITSTATUS(stat); if (WIFEXITED (stat)) return ret; return -1; } /*}}}*/ #endif #endif /* JED_HAS_SUBPROCESSES */