diff options
author | Richard M. Stallman <rms@gnu.org> | 1994-11-14 01:32:24 +0000 |
---|---|---|
committer | Richard M. Stallman <rms@gnu.org> | 1994-11-14 01:32:24 +0000 |
commit | 10bdb74ebd1b848ed6f3dae4b199881812b71527 (patch) | |
tree | 7201affd4f2024765a9ba0fb13839b973afae811 /src/w32proc.c | |
parent | 9b90f439ac1b04add3470df90392c9268cd1b999 (diff) | |
download | emacs-10bdb74ebd1b848ed6f3dae4b199881812b71527.tar.gz |
Initial revision
Diffstat (limited to 'src/w32proc.c')
-rw-r--r-- | src/w32proc.c | 780 |
1 files changed, 780 insertions, 0 deletions
diff --git a/src/w32proc.c b/src/w32proc.c new file mode 100644 index 00000000000..cfed163a656 --- /dev/null +++ b/src/w32proc.c @@ -0,0 +1,780 @@ +/* Process support for Windows NT port of GNU EMACS. + Copyright (C) 1992 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, 675 Mass Ave, Cambridge, MA 02139, USA. + + Drew Bliss Oct 14, 1993 + Adapted from alarm.c by Tim Fleehart +*/ + +#include <stdio.h> +#include <stdlib.h> +#include <errno.h> +#include <io.h> +#include <signal.h> + +#include "config.h" + +#include <windows.h> + +#include "lisp.h" +#include "nt.h" +#include "systime.h" + +/* #define FULL_DEBUG */ + +typedef void (_CALLBACK_ *signal_handler)(int); + +/* Defined in process.h which conflicts with the local copy */ +#define _P_NOWAIT 1 + +typedef struct _child_process +{ + int fd; + HANDLE char_avail; + HANDLE char_consumed; + char chr; + BOOL status; + HANDLE process; + DWORD pid; + HANDLE thrd; +} child_process; + +#define MAX_CHILDREN MAXDESC + +#ifdef EMACSDEBUG +void _CRTAPI1 +_DebPrint (char *fmt, ...) +{ + char buf[256]; + va_list args; + + va_start (args, fmt); + vsprintf (buf, fmt, args); + va_end (args); + OutputDebugString (buf); +} +#endif + +/* Child process management list. */ +static int child_proc_count = 0; +static child_process child_procs[MAX_CHILDREN]; +static child_process *dead_child = NULL; + +#define CHILD_ACTIVE(cp) ((cp)->process != NULL) +#define DEACTIVATE_CHILD(cp) ((cp)->process = NULL) + +/* Signal handlers...SIG_DFL == 0 so this is initialized correctly. */ +static signal_handler sig_handlers[NSIG]; + +/* Fake signal implementation to record the SIGCHLD handler. */ +signal_handler +win32_signal (int sig, signal_handler handler) +{ + signal_handler old; + + if (sig != SIGCHLD) + { + errno = EINVAL; + return SIG_ERR; + } + old = sig_handlers[sig]; + sig_handlers[sig] = handler; + return old; +} + +/* Find an unused process slot. */ +static child_process * +new_child (void) +{ + child_process *cp; + + if (child_proc_count == MAX_CHILDREN) + return NULL; + + for (cp = child_procs+(child_proc_count-1); cp >= child_procs; cp--) + if (!CHILD_ACTIVE (cp)) + return cp; + return &child_procs[child_proc_count++]; +} + +/* Find a child by pid. */ +static child_process * +find_child_pid (DWORD pid) +{ + child_process *cp; + + for (cp = child_procs+(child_proc_count-1); cp >= child_procs; cp--) + if (CHILD_ACTIVE (cp) && pid == cp->pid) + return cp; + return NULL; +} + +/* Find a child by fd. */ +static child_process * +find_child_fd (int fd) +{ + child_process *cp; + + for (cp = child_procs+(child_proc_count-1); cp >= child_procs; cp--) + if (CHILD_ACTIVE (cp) && fd == cp->fd) + return cp; + return NULL; +} + +/* Thread proc for child process reader threads + The threads just sit in a loop waiting for input + When they detect input, they signal the char_avail input to + wake up the select emulator + When the select emulator processes their input, it pulses + char_consumed so that the reader thread goes back to reading. */ +DWORD WINAPI +reader_thread (void *arg) +{ + child_process *cp; + + /* Our identity */ + cp = (child_process *)arg; + + /* We have to wait for the go-ahead before we can start */ + if (WaitForSingleObject (cp->char_consumed, INFINITE) != WAIT_OBJECT_0) + return 0; + /* If something went wrong, quit */ + if (!cp->status) + return 0; + + for (;;) + { + /* Use read to get CRLF translation */ + if (read (cp->fd, &cp->chr, sizeof (char)) == sizeof (char)) + { + cp->status = TRUE; + } + else + { +#ifdef FULL_DEBUG + DebPrint (("reader_thread.read failed with %lu for fd %ld\n", + GetLastError (), cp->fd)); +#endif + cp->status = FALSE; + } + + if (!SetEvent (cp->char_avail)) + { + DebPrint (("reader_thread.SetEvent failed with %lu for fd %ld\n", + GetLastError (), cp->fd)); + break; + } + + /* If the read died, the child has died so let the thread die */ + if (!cp->status) + break; + + /* Wait until our input is acknowledged before reading again */ + if (WaitForSingleObject (cp->char_consumed, INFINITE) != WAIT_OBJECT_0) + { + DebPrint (("reader_thread.WaitForSingleObject failed with " + "%lu for fd %ld\n", GetLastError (), cp->fd)); + break; + } + } + return 0; +} + +static BOOL +create_child (char *exe, char *cmdline, char *env, + PROCESS_INFORMATION *info) +{ + child_process *cp; + DWORD id; + STARTUPINFO start; + SECURITY_ATTRIBUTES sec_attrs; + SECURITY_DESCRIPTOR sec_desc; + + cp = new_child (); + if (cp == NULL) + goto EH_Fail; + + cp->fd = -1; + + cp->char_avail = CreateEvent (NULL, FALSE, FALSE, NULL); + if (cp->char_avail == NULL) + goto EH_Fail; + + cp->char_consumed = CreateEvent (NULL, FALSE, FALSE, NULL); + if (cp->char_consumed == NULL) + goto EH_char_avail; + + cp->thrd = CreateThread (NULL, 1024, reader_thread, cp, 0, &id); + if (cp->thrd == NULL) + goto EH_char_consumed; + + memset (&start, 0, sizeof (start)); + start.cb = sizeof (start); + + /* Explicitly specify no security */ + if (!InitializeSecurityDescriptor (&sec_desc, SECURITY_DESCRIPTOR_REVISION)) + goto EH_thrd; + if (!SetSecurityDescriptorDacl (&sec_desc, TRUE, NULL, FALSE)) + goto EH_thrd; + sec_attrs.nLength = sizeof (sec_attrs); + sec_attrs.lpSecurityDescriptor = &sec_desc; + sec_attrs.bInheritHandle = FALSE; + + if (!CreateProcess (exe, cmdline, &sec_attrs, NULL, TRUE, + CREATE_NEW_PROCESS_GROUP, env, NULL, + &start, info)) + goto EH_thrd; + cp->process = info->hProcess; + cp->pid = info->dwProcessId; + + return TRUE; + + EH_thrd: + id = GetLastError (); + + cp->status = FALSE; + SetEvent (cp->char_consumed); + EH_char_consumed: + CloseHandle (cp->char_consumed); + EH_char_avail: + CloseHandle (cp->char_avail); + EH_Fail: + return FALSE; +} + +/* create_child doesn't know what emacs' file handle will be for waiting + on output from the child, so we need to make this additional call + to register the handle with the process + This way the select emulator knows how to match file handles with + entries in child_procs. */ +void +register_child (int pid, int fd) +{ + child_process *cp; + + cp = find_child_pid (pid); + if (cp == NULL) + { + DebPrint (("register_child unable to find pid %lu\n", pid)); + return; + } + +#ifdef FULL_DEBUG + DebPrint (("register_child registered fd %d with pid %lu\n", fd, pid)); +#endif + + cp->fd = fd; + cp->status = TRUE; + + /* Tell the reader thread to start */ + if (!SetEvent (cp->char_consumed)) + { + DebPrint (("register_child.SetEvent failed with %lu for fd %ld\n", + GetLastError (), cp->fd)); + } +} + +/* When a process dies its pipe will break so the reader thread will + signal failure to the select emulator. + The select emulator then calls this routine to clean up. + Since the thread signaled failure we can assume it is exiting. */ +static void +remove_child (child_process *cp) +{ + /* Reap the thread */ + if (WaitForSingleObject (cp->thrd, INFINITE) != WAIT_OBJECT_0) + { + DebPrint (("remove_child.WaitForSingleObject (thread) failed " + "with %lu for fd %ld\n", GetLastError (), cp->fd)); + } + CloseHandle (cp->thrd); + CloseHandle (cp->char_consumed); + CloseHandle (cp->char_avail); + + /* Reap the process */ + if (WaitForSingleObject (cp->process, INFINITE) != WAIT_OBJECT_0) + { + DebPrint (("remove_child.WaitForSingleObject (process) failed " + "with %lu for fd %ld\n", GetLastError (), cp->fd)); + } + CloseHandle (cp->process); + + DEACTIVATE_CHILD (cp); +} + +/* Wait for any of our existing child processes to die + When it does, close its handle + Return the pid and fill in the status if non-NULL. */ +int +win32_wait (int *status) +{ + DWORD active, retval; + int nh; + child_process *cp, *cps[MAX_CHILDREN]; + HANDLE wait_hnd[MAX_CHILDREN]; + + nh = 0; + if (dead_child != NULL) + { + /* We want to wait for a specific child */ + wait_hnd[nh] = dead_child->process; + cps[nh] = dead_child; + nh++; + } + else + { + for (cp = child_procs+(child_proc_count-1); cp >= child_procs; cp--) + if (CHILD_ACTIVE (cp)) + { + wait_hnd[nh] = cp->process; + cps[nh] = cp; + nh++; + } + } + + if (nh == 0) + { + /* Nothing to wait on, so fail */ + errno = ECHILD; + return -1; + } + + active = WaitForMultipleObjects (nh, wait_hnd, FALSE, INFINITE); + if (active == WAIT_FAILED) + { + errno = EBADF; + return -1; + } + else if (active == WAIT_TIMEOUT) + { + /* Should never happen */ + errno = EINVAL; + return -1; + } + else if (active >= WAIT_OBJECT_0 && + active < WAIT_OBJECT_0+MAXIMUM_WAIT_OBJECTS) + { + active -= WAIT_OBJECT_0; + } + else if (active >= WAIT_ABANDONED_0 && + active < WAIT_ABANDONED_0+MAXIMUM_WAIT_OBJECTS) + { + active -= WAIT_ABANDONED_0; + } + + if (!GetExitCodeProcess (wait_hnd[active], &retval)) + { + DebPrint (("Wait.GetExitCodeProcess failed with %lu\n", + GetLastError ())); + retval = 1; + } + if (retval == STILL_ACTIVE) + { + /* Should never happen */ + DebPrint (("Wait.WaitForMultipleObjects returned an active process\n")); + errno = EINVAL; + return -1; + } + + cp = cps[active]; +#ifdef FULL_DEBUG + DebPrint (("Wait signaled with process pid %d\n", cp->pid)); +#endif + + if (status) + { + /* In process.c the default WAITTYPE is defined. + Since we can't determine anything about why a process died + we can only return a code that looks like WIFEXITED */ + *status = (retval & 0x7fffff) << 8; + } + + return cp->pid; +} + +/* We pass our process ID to our children by setting up an environment + variable in their environment. */ +char ppid_env_var_buffer[64]; + +/* When a new child process is created we need to register it in our list, + so intercept spawn requests. */ +int +win32_spawnve (int mode, char *cmdname, char **argv, char **envp) +{ + char *cmdline, *env, *parg, **targ; + int arglen; + PROCESS_INFORMATION pi; + + if (child_proc_count == MAX_CHILDREN) + { + errno = EAGAIN; + return -1; + } + + /* We don't care about the other modes */ + if (mode != _P_NOWAIT) + { + errno = EINVAL; + return -1; + } + + /* we have to do some conjuring here to put argv and envp into the + form CreateProcess wants... argv needs to be a space separated/null + terminated list of parameters, and envp is a null + separated/double-null terminated list of parameters. + + Since I have no idea how large argv and envp are likely to be + we figure out list lengths on the fly and allocate them. */ + + /* do argv... */ + arglen = 0; + targ = argv; + while (*targ) + { + arglen += strlen (*targ++) + 1; + } + cmdline = malloc (arglen); + if (cmdline == NULL) + { + errno = ENOMEM; + goto EH_Fail; + } + targ = argv; + parg = cmdline; + while (*targ) + { + strcpy (parg, *targ); + parg += strlen (*targ++); + *parg++ = ' '; + } + *--parg = '\0'; + + /* and envp... */ + arglen = 1; + targ = envp; + while (*targ) + { + arglen += strlen (*targ++) + 1; + } + sprintf (ppid_env_var_buffer, "__PARENT_PROCESS_ID=%d", + GetCurrentProcessId ()); + arglen += strlen (ppid_env_var_buffer) + 1; + + env = malloc (arglen); + if (env == NULL) + { + errno = ENOMEM; + goto EH_cmdline; + } + targ = envp; + parg = env; + while (*targ) + { + strcpy (parg, *targ); + parg += strlen (*targ++); + *parg++ = '\0'; + } + strcpy (parg, ppid_env_var_buffer); + parg += strlen (ppid_env_var_buffer); + *parg++ = '\0'; + *parg = '\0'; + + /* Now create the process. */ + if (!create_child (cmdname, cmdline, env, &pi)) + { + errno = ENOEXEC; + goto EH_env; + } + + return pi.dwProcessId; + + EH_env: + free (env); + EH_cmdline: + free (cmdline); + EH_Fail: + return -1; +} + +/* Emulate the select call + Wait for available input on any of the given rfds, or timeout if + a timeout is given and no input is detected + wfds and efds are not supported and must be NULL. */ + +/* From ntterm.c */ +extern HANDLE keyboard_handle; +/* From process.c */ +extern int proc_buffered_char[]; + +int +select (int nfds, SELECT_TYPE *rfds, SELECT_TYPE *wfds, SELECT_TYPE *efds, + EMACS_TIME *timeout) +{ + SELECT_TYPE orfds; + DWORD timeout_ms; + int i, nh, nr; + DWORD active; + child_process *cp, *cps[MAX_CHILDREN]; + HANDLE wait_hnd[MAX_CHILDREN]; + + /* If the descriptor sets are NULL but timeout isn't, then just Sleep. */ + if (rfds == NULL && wfds == NULL && efds == NULL && timeout != NULL) + { + Sleep ((*timeout) * 1000); + return 0; + } + + /* Otherwise, we only handle rfds, so fail otherwise. */ + if (rfds == NULL || wfds != NULL || efds != NULL) + { + errno = EINVAL; + return -1; + } + + orfds = *rfds; + FD_ZERO (rfds); + nr = 0; + + /* Build a list of handles to wait on. */ + nh = 0; + for (i = 0; i < nfds; i++) + if (FD_ISSET (i, &orfds)) + { + if (i == 0) + { + /* Handle stdin specially */ + wait_hnd[nh] = keyboard_handle; + cps[nh] = NULL; + nh++; + + /* Check for any emacs-generated input in the queue since + it won't be detected in the wait */ + if (detect_input_pending ()) + { + FD_SET (i, rfds); + nr++; + } + } + else + { + /* Child process input */ + cp = find_child_fd (i); + if (cp) + { +#ifdef FULL_DEBUG + DebPrint (("select waiting on child %d fd %d\n", + cp-child_procs, i)); +#endif + wait_hnd[nh] = cp->char_avail; + cps[nh] = cp; + nh++; + } + else + { + /* Unable to find something to wait on for this fd, fail */ + DebPrint (("select unable to find child process " + "for fd %ld\n", i)); + nh = 0; + break; + } + } + } + + /* Nothing to look for, so we didn't find anything */ + if (nh == 0) + { + Sleep ((*timeout) * 1000); + return 0; + } + + /* Check for immediate return without waiting */ + if (nr > 0) + return nr; + + /* + Wait for input + If a child process dies while this is waiting, its pipe will break + so the reader thread will signal an error condition, thus, the wait + will wake up + */ + timeout_ms = timeout ? *timeout*1000 : INFINITE; + active = WaitForMultipleObjects (nh, wait_hnd, FALSE, timeout_ms); + if (active == WAIT_FAILED) + { + DebPrint (("select.WaitForMultipleObjects (%d, %lu) failed with %lu\n", + nh, timeout_ms, GetLastError ())); + /* Is there a better error? */ + errno = EBADF; + return -1; + } + else if (active == WAIT_TIMEOUT) + { + return 0; + } + else if (active >= WAIT_OBJECT_0 && + active < WAIT_OBJECT_0+MAXIMUM_WAIT_OBJECTS) + { + active -= WAIT_OBJECT_0; + } + else if (active >= WAIT_ABANDONED_0 && + active < WAIT_ABANDONED_0+MAXIMUM_WAIT_OBJECTS) + { + active -= WAIT_ABANDONED_0; + } + + if (cps[active] == NULL) + { + /* Keyboard input available */ + FD_SET (0, rfds); + nr++; + + /* This shouldn't be necessary, but apparently just setting the input + fd is not good enough for emacs */ + read_input_waiting (); + } + else + { + /* Child process */ + cp = cps[active]; + + /* If status is FALSE the read failed so don't report input */ + if (cp->status) + { + FD_SET (cp->fd, rfds); + proc_buffered_char[cp->fd] = cp->chr; + nr++; + } + else + { + /* The SIGCHLD handler will do a Wait so we know it won't + return until the process is dead + We force Wait to only wait for this process to avoid it + picking up other children that happen to be dead but that + we haven't noticed yet + SIG_DFL for SIGCHLD is ignore? */ + if (sig_handlers[SIGCHLD] != SIG_DFL && + sig_handlers[SIGCHLD] != SIG_IGN) + { +#ifdef FULL_DEBUG + DebPrint (("select calling SIGCHLD handler for pid %d\n", + cp->pid)); +#endif + dead_child = cp; + sig_handlers[SIGCHLD](SIGCHLD); + dead_child = NULL; + } + + /* Clean up the child process entry in the table */ + remove_child (cp); + } + } + return nr; +} + +/* + Substitute for certain kill () operations + */ +int +win32_kill_process (int pid, int sig) +{ + child_process *cp; + + /* Only handle signals that will result in the process dying */ + if (sig != SIGINT && sig != SIGKILL && sig != SIGQUIT && sig != SIGHUP) + { + errno = EINVAL; + return -1; + } + + cp = find_child_pid (pid); + if (cp == NULL) + { + DebPrint (("win32_kill_process didn't find a child with pid %lu\n", pid)); + errno = ECHILD; + return -1; + } + + if (sig == SIGINT) + { + /* Fake Ctrl-Break. */ + if (!GenerateConsoleCtrlEvent (CTRL_BREAK_EVENT, pid)) + { + DebPrint (("win32_kill_process.GenerateConsoleCtrlEvent return %d " + "for pid %lu\n", GetLastError (), pid)); + errno = EINVAL; + return -1; + } + } + else + { + /* Kill the process. On Win32 this doesn't kill child processes + so it doesn't work very well for shells which is why it's + not used in every case. */ + if (!TerminateProcess (cp->process, 0xff)) + { + DebPrint (("win32_kill_process.TerminateProcess returned %d " + "for pid %lu\n", GetLastError (), pid)); + errno = EINVAL; + return -1; + } + } + return 0; +} + +/* If the channel is a pipe this read might block since we don't + know how many characters are available, so check and read only + what's there + We also need to wake up the reader thread once we've read our data. */ +int +read_child_output (int fd, char *buf, int max) +{ + HANDLE h; + int to_read, nchars; + DWORD waiting; + child_process *cp; + + h = (HANDLE)_get_osfhandle (fd); + if (GetFileType (h) == FILE_TYPE_PIPE) + { + PeekNamedPipe (h, NULL, 0, NULL, &waiting, NULL); + to_read = min (waiting, (DWORD)max); + } + else + to_read = max; + + /* Use read to get CRLF translation */ + nchars = read (fd, buf, to_read); + + if (GetFileType (h) == FILE_TYPE_PIPE) + { + /* Wake up the reader thread + for this process */ + cp = find_child_fd (fd); + if (cp) + { + if (!SetEvent (cp->char_consumed)) + DebPrint (("read_child_output.SetEvent failed with " + "%lu for fd %ld\n", GetLastError (), fd)); + } + else + DebPrint (("read_child_output couldn't find a child with fd %d\n", + fd)); + } + + return nchars; +} |