diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/w32proc.c | 869 |
1 files changed, 523 insertions, 346 deletions
diff --git a/src/w32proc.c b/src/w32proc.c index d13a5bca9e8..ee0f5f7986b 100644 --- a/src/w32proc.c +++ b/src/w32proc.c @@ -22,14 +22,21 @@ Boston, MA 02111-1307, USA. Adapted from alarm.c by Tim Fleehart */ -#include <config.h> - #include <stdio.h> #include <stdlib.h> #include <errno.h> #include <io.h> +#include <fcntl.h> #include <signal.h> +/* must include CRT headers *before* config.h */ +#include "config.h" +#undef signal +#undef wait +#undef spawnve +#undef select +#undef kill + #include <windows.h> #include "lisp.h" @@ -42,32 +49,10 @@ Boston, MA 02111-1307, USA. extern char *sys_siglist[]; #endif -/* #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, ...) +void _DebPrint (const char *fmt, ...) { - char buf[256]; + char buf[1024]; va_list args; va_start (args, fmt); @@ -77,20 +62,14 @@ _DebPrint (char *fmt, ...) } #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) +typedef void (_CALLBACK_ *signal_handler)(int); /* 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) +sys_signal (int sig, signal_handler handler) { signal_handler old; @@ -104,19 +83,109 @@ win32_signal (int sig, signal_handler handler) return old; } +/* Defined in <process.h> which conflicts with the local copy */ +#define _P_NOWAIT 1 + +/* Child process management list. */ +int child_proc_count = 0; +child_process child_procs[ MAX_CHILDREN ]; +child_process *dead_child = NULL; + +DWORD WINAPI reader_thread (void *arg); + /* Find an unused process slot. */ -static child_process * +child_process * new_child (void) { child_process *cp; - - if (child_proc_count == MAX_CHILDREN) - return NULL; + DWORD id; for (cp = child_procs+(child_proc_count-1); cp >= child_procs; cp--) if (!CHILD_ACTIVE (cp)) - return cp; - return &child_procs[child_proc_count++]; + goto Initialise; + if (child_proc_count == MAX_CHILDREN) + return NULL; + cp = &child_procs[child_proc_count++]; + + Initialise: + memset (cp, 0, sizeof(*cp)); + cp->fd = -1; + cp->pid = -1; + cp->procinfo.hProcess = NULL; + cp->status = STATUS_READ_ERROR; + + /* use manual reset event so that select() will function properly */ + cp->char_avail = CreateEvent (NULL, TRUE, FALSE, NULL); + if (cp->char_avail) + { + cp->char_consumed = CreateEvent (NULL, FALSE, FALSE, NULL); + if (cp->char_consumed) + { + cp->thrd = CreateThread (NULL, 1024, reader_thread, cp, 0, &id); + if (cp->thrd) + return cp; + } + } + delete_child (cp); + return NULL; +} + +void +delete_child (child_process *cp) +{ + int i; + + /* Should not be deleting a child that is still needed. */ + for (i = 0; i < MAXDESC; i++) + if (fd_info[i].cp == cp) + abort (); + + if (!CHILD_ACTIVE (cp)) + return; + + /* reap thread if necessary */ + if (cp->thrd) + { + DWORD rc; + + if (GetExitCodeThread (cp->thrd, &rc) && rc == STILL_ACTIVE) + { + /* let the thread exit cleanly if possible */ + cp->status = STATUS_READ_ERROR; + SetEvent (cp->char_consumed); + if (WaitForSingleObject (cp->thrd, 1000) != WAIT_OBJECT_0) + { + DebPrint (("delete_child.WaitForSingleObject (thread) failed " + "with %lu for fd %ld\n", GetLastError (), cp->fd)); + TerminateThread (cp->thrd, 0); + } + } + CloseHandle (cp->thrd); + cp->thrd = NULL; + } + if (cp->char_avail) + { + CloseHandle (cp->char_avail); + cp->char_avail = NULL; + } + if (cp->char_consumed) + { + CloseHandle (cp->char_consumed); + cp->char_consumed = NULL; + } + + /* update child_proc_count (highest numbered slot in use plus one) */ + if (cp == child_procs + child_proc_count - 1) + { + for (i = child_proc_count-1; i >= 0; i--) + if (CHILD_ACTIVE (&child_procs[i])) + { + child_proc_count = i + 1; + break; + } + } + if (i < 0) + child_proc_count = 0; } /* Find a child by pid. */ @@ -124,31 +193,18 @@ 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. */ +/* Thread proc for child process and socket reader threads. Each thread + is normally blocked until woken by select() to check for input by + reading one char. When the read completes, char_avail is signalled + to wake up the select emulator and the thread blocks itself again. */ DWORD WINAPI reader_thread (void *arg) { @@ -158,37 +214,30 @@ reader_thread (void *arg) 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; - + if (cp == NULL || + WaitForSingleObject (cp->char_consumed, INFINITE) != WAIT_OBJECT_0) + return 1; + 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; - } - + int rc; + + rc = _sys_read_ahead (cp->fd); + + /* The name char_avail is a misnomer - it really just means the + read-ahead has completed, whether successfully or not. */ if (!SetEvent (cp->char_avail)) { DebPrint (("reader_thread.SetEvent failed with %lu for fd %ld\n", GetLastError (), cp->fd)); - break; - } + return 1; + } + + if (rc == STATUS_READ_ERROR) + return 1; /* If the read died, the child has died so let the thread die */ - if (!cp->status) + if (rc == STATUS_READ_FAILED) break; /* Wait until our input is acknowledged before reading again */ @@ -204,31 +253,13 @@ reader_thread (void *arg) static BOOL create_child (char *exe, char *cmdline, char *env, - PROCESS_INFORMATION *info) + int * pPid, child_process *cp) { - 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; + if (cp == NULL) abort (); memset (&start, 0, sizeof (start)); start.cb = sizeof (start); @@ -244,32 +275,35 @@ create_child (char *exe, char *cmdline, char *env, /* Explicitly specify no security */ if (!InitializeSecurityDescriptor (&sec_desc, SECURITY_DESCRIPTOR_REVISION)) - goto EH_thrd; + goto EH_Fail; if (!SetSecurityDescriptorDacl (&sec_desc, TRUE, NULL, FALSE)) - goto EH_thrd; + goto EH_Fail; 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; + CREATE_NEW_PROCESS_GROUP, + env, NULL, + &start, &cp->procinfo)) + goto EH_Fail; + + cp->pid = (int) cp->procinfo.dwProcessId; + + /* Hack for Windows 95, which assigns large (ie negative) pids */ + if (cp->pid < 0) + cp->pid = -cp->pid; + + /* pid must fit in a Lisp_Int */ + cp->pid = (cp->pid & VALMASK); + + + *pPid = cp->pid; 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: + DebPrint (("create_child.CreateProcess failed: %ld\n", GetLastError());); return FALSE; } @@ -295,14 +329,19 @@ register_child (int pid, int fd) #endif cp->fd = fd; - cp->status = TRUE; - /* Tell the reader thread to start */ - if (!SetEvent (cp->char_consumed)) + /* thread is initially blocked until select is called; set status so + that select will release thread */ + cp->status = STATUS_READ_ACKNOWLEDGED; + + /* attach child_process to fd_info */ + if (fd_info[fd].cp != NULL) { - DebPrint (("register_child.SetEvent failed with %lu for fd %ld\n", - GetLastError (), cp->fd)); + DebPrint (("register_child: fd_info[%d] apparently in use!\n", fd)); + abort (); } + + fd_info[fd].cp = cp; } /* When a process dies its pipe will break so the reader thread will @@ -310,27 +349,26 @@ register_child (int pid, int fd) 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_subprocess (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) + if (cp->procinfo.hProcess) { - DebPrint (("remove_child.WaitForSingleObject (process) failed " - "with %lu for fd %ld\n", GetLastError (), cp->fd)); + /* Reap the process */ + if (WaitForSingleObject (cp->procinfo.hProcess, INFINITE) != WAIT_OBJECT_0) + DebPrint (("reap_subprocess.WaitForSingleObject (process) failed " + "with %lu for fd %ld\n", GetLastError (), cp->fd)); + CloseHandle (cp->procinfo.hProcess); + cp->procinfo.hProcess = NULL; + CloseHandle (cp->procinfo.hThread); + cp->procinfo.hThread = NULL; } - CloseHandle (cp->process); - - DEACTIVATE_CHILD (cp); + + /* For asynchronous children, the child_proc resources will be freed + when the last pipe read descriptor is closed; for synchronous + children, we must explicitly free the resources now because + register_child has not been called. */ + if (cp->fd == -1) + delete_child (cp); } /* Wait for any of our existing child processes to die @@ -338,10 +376,11 @@ remove_child (child_process *cp) Return the pid and fill in the status if non-NULL. */ int -win32_wait (int *status) +sys_wait (int *status) { DWORD active, retval; int nh; + int pid; child_process *cp, *cps[MAX_CHILDREN]; HANDLE wait_hnd[MAX_CHILDREN]; @@ -349,17 +388,20 @@ win32_wait (int *status) if (dead_child != NULL) { /* We want to wait for a specific child */ - wait_hnd[nh] = dead_child->process; + wait_hnd[nh] = dead_child->procinfo.hProcess; cps[nh] = dead_child; + if (!wait_hnd[nh]) abort (); nh++; } else { for (cp = child_procs+(child_proc_count-1); cp >= child_procs; cp--) - if (CHILD_ACTIVE (cp)) + /* some child_procs might be sockets; ignore them */ + if (CHILD_ACTIVE (cp) && cp->procinfo.hProcess) { - wait_hnd[nh] = cp->process; + wait_hnd[nh] = cp->procinfo.hProcess; cps[nh] = cp; + if (!wait_hnd[nh]) abort (); nh++; } } @@ -416,8 +458,12 @@ win32_wait (int *status) retval = SIGINT; else retval <<= 8; - + cp = cps[active]; + pid = cp->pid; +#ifdef FULL_DEBUG + DebPrint (("Wait signaled with process pid %d\n", cp->pid)); +#endif if (status) { @@ -445,15 +491,11 @@ win32_wait (int *status) synch_process_death = signame; } - TerminateThread (cp->thrd, 0); - CloseHandle (cp->thrd); - CloseHandle (cp->char_consumed); - CloseHandle (cp->char_avail); - CloseHandle (cp->process); - DEACTIVATE_CHILD (cp); + + reap_subprocess (cp); } - return cp->pid; + return pid; } /* We pass our process ID to our children by setting up an environment @@ -463,12 +505,20 @@ 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) +sys_spawnve (int mode, char *cmdname, char **argv, char **envp) { Lisp_Object program, full; char *cmdline, *env, *parg, **targ; int arglen; - PROCESS_INFORMATION pi; + int pid; + child_process *cp; + + /* We don't care about the other modes */ + if (mode != _P_NOWAIT) + { + errno = EINVAL; + return -1; + } /* Handle executable names without an executable suffix. */ program = make_string (cmdname, strlen (cmdname)); @@ -489,23 +539,24 @@ win32_spawnve (int mode, char *cmdname, char **argv, char **envp) argv[0] = cmdname; } - 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; - } + /* make sure cmdname is in DOS format */ + strcpy (cmdname = alloca (strlen (cmdname) + 1), argv[0]); + unixtodos_filename (cmdname); + argv[0] = cmdname; /* 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. + + Additionally, zero-length args and args containing whitespace need + to be wrapped in double quotes. Args containing embedded double + quotes (as opposed to enclosing quotes, which we leave alone) are + usually illegal (most Win32 programs do not implement escaping of + double quotes - sad but true, at least for programs compiled with + MSVC), but we will escape quotes anyway for those programs that can + handle it. The Win32 gcc library from Cygnus doubles quotes to + escape them, so we will use that convention. 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. */ @@ -515,21 +566,69 @@ win32_spawnve (int mode, char *cmdname, char **argv, char **envp) targ = argv; while (*targ) { + char * p = *targ; + int add_quotes = 0; + + if (*p == 0) + add_quotes = 1; + while (*p) + if (*p++ == '"') + { + /* allow for embedded quotes to be doubled - we won't + actually double quotes that aren't embedded though */ + arglen++; + add_quotes = 1; + } + else if (*p == ' ' || *p == '\t') + add_quotes = 1; + if (add_quotes) + arglen += 2; arglen += strlen (*targ++) + 1; } - cmdline = malloc (arglen); - if (cmdline == NULL) - { - errno = ENOMEM; - goto EH_Fail; - } + cmdline = alloca (arglen); targ = argv; parg = cmdline; while (*targ) { - strcpy (parg, *targ); - parg += strlen (*targ++); + char * p = *targ; + int add_quotes = 0; + + if (*p == 0) + add_quotes = 1; +#if 0 + /* Unfortunately, this causes more problems than it solves, + because argv arrays are not always carefully constructed. + grep, for instance, passes the whole command line as one + argument, so it becomes impossible to pass a regexp which + contains spaces. */ + for ( ; *p; p++) + if (*p == ' ' || *p == '\t' || *p == '"') + add_quotes = 1; +#endif + if (add_quotes) + { + char * first; + char * last; + + p = *targ; + first = p; + last = p + strlen (p) - 1; + *parg++ = '"'; + while (*p) + { + if (*p == '"' && p > first && p < last) + *parg++ = '"'; /* double up embedded quotes only */ + *parg++ = *p++; + } + *parg++ = '"'; + } + else + { + strcpy (parg, *targ); + parg += strlen (*targ); + } *parg++ = ' '; + targ++; } *--parg = '\0'; @@ -544,12 +643,7 @@ win32_spawnve (int mode, char *cmdname, char **argv, char **envp) GetCurrentProcessId ()); arglen += strlen (ppid_env_var_buffer) + 1; - env = malloc (arglen); - if (env == NULL) - { - errno = ENOMEM; - goto EH_cmdline; - } + env = alloca (arglen); targ = envp; parg = env; while (*targ) @@ -562,22 +656,23 @@ win32_spawnve (int mode, char *cmdname, char **argv, char **envp) parg += strlen (ppid_env_var_buffer); *parg++ = '\0'; *parg = '\0'; + + cp = new_child (); + if (cp == NULL) + { + errno = EAGAIN; + return -1; + } /* Now create the process. */ - if (!create_child (cmdname, cmdline, env, &pi)) + if (!create_child (cmdname, cmdline, env, &pid, cp)) { + delete_child (cp); errno = ENOEXEC; - goto EH_env; + return -1; } - return pi.dwProcessId; - - EH_env: - free (env); - EH_cmdline: - free (cmdline); - EH_Fail: - return -1; + return pid; } /* Emulate the select call @@ -598,20 +693,14 @@ sys_select (int nfds, SELECT_TYPE *rfds, SELECT_TYPE *wfds, SELECT_TYPE *efds, DWORD timeout_ms; int i, nh, nr; DWORD active; - child_process *cp, *cps[MAX_CHILDREN + 1]; - HANDLE wait_hnd[MAX_CHILDREN + 1]; -#ifdef HAVE_NTGUI1 - BOOL keyboardwait = FALSE ; -#endif /* HAVE_NTGUI */ + child_process *cp; + HANDLE wait_hnd[MAXDESC]; + int fdindex[MAXDESC]; /* mapping from wait handles back to descriptors */ /* If the descriptor sets are NULL but timeout isn't, then just Sleep. */ if (rfds == NULL && wfds == NULL && efds == NULL && timeout != NULL) { -#ifdef HAVE_TIMEVAL Sleep (timeout->tv_sec * 1000 + timeout->tv_usec / 1000); -#else - Sleep ((*timeout) * 1000); -#endif return 0; } @@ -633,67 +722,96 @@ sys_select (int nfds, SELECT_TYPE *rfds, SELECT_TYPE *wfds, SELECT_TYPE *efds, { if (i == 0) { -#ifdef HAVE_NTGUI1 - keyboardwait = TRUE ; -#else - /* Handle stdin specially */ - wait_hnd[nh] = keyboard_handle; - cps[nh] = NULL; - nh++; -#endif /* HAVE_NTGUI */ + if (keyboard_handle) + { + /* Handle stdin specially */ + wait_hnd[nh] = keyboard_handle; + fdindex[nh] = i; + 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++; + return 1; } } else { - /* Child process input */ - cp = find_child_fd (i); + /* Child process and socket input */ + cp = fd_info[i].cp; if (cp) { + int current_status = cp->status; + + if (current_status == STATUS_READ_ACKNOWLEDGED) + { + /* Tell reader thread which file handle to use. */ + cp->fd = i; + /* Wake up the reader thread for this process */ + cp->status = STATUS_READ_READY; + if (!SetEvent (cp->char_consumed)) + DebPrint (("nt_select.SetEvent failed with " + "%lu for fd %ld\n", GetLastError (), i)); + } + +#ifdef CHECK_INTERLOCK + /* slightly crude cross-checking of interlock between threads */ + + current_status = cp->status; + if (WaitForSingleObject (cp->char_avail, 0) == WAIT_OBJECT_0) + { + /* char_avail has been signalled, so status (which may + have changed) should indicate read has completed + but has not been acknowledged. */ + current_status = cp->status; + if (current_status != STATUS_READ_SUCCEEDED && + current_status != STATUS_READ_FAILED) + DebPrint (("char_avail set, but read not completed: status %d\n", + current_status)); + } + else + { + /* char_avail has not been signalled, so status should + indicate that read is in progress; small possibility + that read has completed but event wasn't yet signalled + when we tested it (because a context switch occurred + or if running on separate CPUs). */ + if (current_status != STATUS_READ_READY && + current_status != STATUS_READ_IN_PROGRESS && + current_status != STATUS_READ_SUCCEEDED && + current_status != STATUS_READ_FAILED) + DebPrint (("char_avail reset, but read status is bad: %d\n", + current_status)); + } +#endif + wait_hnd[nh] = cp->char_avail; + fdindex[nh] = i; + if (!wait_hnd[nh]) abort (); + nh++; #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; + /* Unable to find something to wait on for this fd, skip */ + DebPrint (("sys_select: fd %ld is invalid! ignoring\n", i)); + abort (); } } } - /* Never do this in win32 since we will not get paint messages */ - -#ifndef HAVE_NTGUI1 /* Nothing to look for, so we didn't find anything */ if (nh == 0) { if (timeout) -#ifdef HAVE_TIMEVAL Sleep (timeout->tv_sec * 1000 + timeout->tv_usec / 1000); -#else - Sleep ((*timeout) * 1000); -#endif return 0; } -#endif /* !HAVE_NTGUI */ - - /* Check for immediate return without waiting */ - if (nr > 0) - return nr; /* Wait for input @@ -701,41 +819,25 @@ sys_select (int nfds, SELECT_TYPE *rfds, SELECT_TYPE *wfds, SELECT_TYPE *efds, so the reader thread will signal an error condition, thus, the wait will wake up */ -#ifdef HAVE_TIMEVAL timeout_ms = timeout ? (timeout->tv_sec * 1000 + timeout->tv_usec / 1000) : INFINITE; -#else - timeout_ms = timeout ? *timeout*1000 : INFINITE; -#endif -#ifdef HAVE_NTGUI1 - active = MsgWaitForMultipleObjects (nh, wait_hnd, FALSE, timeout_ms,QS_ALLINPUT); -#else + active = WaitForMultipleObjects (nh, wait_hnd, FALSE, timeout_ms); -#endif /* HAVE_NTGUI */ + if (active == WAIT_FAILED) { DebPrint (("select.WaitForMultipleObjects (%d, %lu) failed with %lu\n", nh, timeout_ms, GetLastError ())); - /* Is there a better error? */ - errno = EBADF; + /* don't return EBADF - this causes wait_reading_process_input to + abort; WAIT_FAILED is returned when single-stepping under + Windows 95 after switching thread focus in debugger, and + possibly at other times. */ + errno = EINTR; return -1; } else if (active == WAIT_TIMEOUT) { return 0; } -#ifdef HAVE_NTGUI1 - else if (active == WAIT_OBJECT_0 + nh) - { - /* Keyboard input available */ - FD_SET (0, rfds); - - /* This shouldn't be necessary, but apparently just setting the input - fd is not good enough for emacs */ -// read_input_waiting (); - - return (1) ; - } -#endif /* HAVE_NTGUI */ else if (active >= WAIT_OBJECT_0 && active < WAIT_OBJECT_0+MAXIMUM_WAIT_OBJECTS) { @@ -746,63 +848,82 @@ sys_select (int nfds, SELECT_TYPE *rfds, SELECT_TYPE *wfds, SELECT_TYPE *efds, { 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 + /* Loop over all handles after active (now officially documented as + being the first signalled handle in the array). We do this to + ensure fairness, so that all channels with data available will be + processed - otherwise higher numbered channels could be starved. */ + do { - /* 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; + if (fdindex[active] == 0) + { + /* Keyboard input available */ + FD_SET (0, rfds); 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) - { + { + /* must be a socket or pipe */ + int current_status; + + cp = fd_info[ fdindex[active] ].cp; + + /* Read ahead should have completed, either succeeding or failing. */ + FD_SET (fdindex[active], rfds); + nr++; + current_status = cp->status; + if (current_status != STATUS_READ_SUCCEEDED) + { + if (current_status != STATUS_READ_FAILED) + DebPrint (("internal error: subprocess pipe signalled " + "at the wrong time (status %d)\n!", current_status)); + + /* The child_process entry for a socket or pipe will be + freed when the last descriptor using it is closed; for + pipes, we call the SIGCHLD handler. */ + if (fd_info[ fdindex[active] ].flags & FILE_PIPE) + { + /* 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)); + 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); - } - } + dead_child = cp; + sig_handlers[SIGCHLD] (SIGCHLD); + dead_child = NULL; + } + + /* Clean up the child process entry in the table */ + reap_subprocess (cp); + } + } + } + + /* Test for input on remaining channels. */ + while (++active < nh) + if (WaitForSingleObject (wait_hnd[active], 0) == WAIT_OBJECT_0) + break; + } while (active < nh); + return nr; } -/* - Substitute for certain kill () operations - */ +/* Substitute for certain kill () operations */ int -win32_kill_process (int pid, int sig) +sys_kill (int pid, int sig) { child_process *cp; + HANDLE proc_hand; + int need_to_free = 0; + int rc = 0; /* Only handle signals that will result in the process dying */ if (sig != SIGINT && sig != SIGKILL && sig != SIGQUIT && sig != SIGHUP) @@ -810,24 +931,33 @@ win32_kill_process (int pid, int sig) 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; + proc_hand = OpenProcess (PROCESS_TERMINATE, 0, pid); + if (proc_hand == NULL) + { + errno = EPERM; + return -1; + } + need_to_free = 1; + } + else + { + proc_hand = cp->procinfo.hProcess; + pid = cp->procinfo.dwProcessId; } if (sig == SIGINT) { - /* Fake Ctrl-Break. */ + /* Ctrl-Break is NT equivalent of SIGINT. */ if (!GenerateConsoleCtrlEvent (CTRL_BREAK_EVENT, pid)) { - DebPrint (("win32_kill_process.GenerateConsoleCtrlEvent return %d " + DebPrint (("sys_kill.GenerateConsoleCtrlEvent return %d " "for pid %lu\n", GetLastError (), pid)); errno = EINVAL; - return -1; + rc = -1; } } else @@ -835,56 +965,103 @@ win32_kill_process (int pid, int sig) /* 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)) + if (!TerminateProcess (proc_hand, 0xff)) { - DebPrint (("win32_kill_process.TerminateProcess returned %d " + DebPrint (("sys_kill.TerminateProcess returned %d " "for pid %lu\n", GetLastError (), pid)); errno = EINVAL; - return -1; + rc = -1; } } - return 0; + + if (need_to_free) + CloseHandle (proc_hand); + + return rc; } -/* 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) +extern int report_file_error (char *, Lisp_Object); + +/* The following two routines are used to manipulate stdin, stdout, and + stderr of our child processes. + + Assuming that in, out, and err are *not* inheritable, we make them + stdin, stdout, and stderr of the child as follows: + + - Save the parent's current standard handles. + - Set the std handles to inheritable duplicates of the ones being passed in. + (Note that _get_osfhandle() is an io.h procedure that retrieves the + NT file handle for a crt file descriptor.) + - Spawn the child, which inherits in, out, and err as stdin, + stdout, and stderr. (see Spawnve) + - Close the std handles passed to the child. + - Reset the parent's standard handles to the saved handles. + (see reset_standard_handles) + We assume that the caller closes in, out, and err after calling us. */ + +void +prepare_standard_handles (int in, int out, int err, HANDLE handles[3]) { - 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; + HANDLE parent; + HANDLE newstdin, newstdout, newstderr; + + parent = GetCurrentProcess (); + + handles[0] = GetStdHandle (STD_INPUT_HANDLE); + handles[1] = GetStdHandle (STD_OUTPUT_HANDLE); + handles[2] = GetStdHandle (STD_ERROR_HANDLE); + + /* make inheritable copies of the new handles */ + if (!DuplicateHandle (parent, + (HANDLE) _get_osfhandle (in), + parent, + &newstdin, + 0, + TRUE, + DUPLICATE_SAME_ACCESS)) + report_file_error ("Duplicating input handle for child", Qnil); - /* Use read to get CRLF translation */ - nchars = read (fd, buf, to_read); + if (!DuplicateHandle (parent, + (HANDLE) _get_osfhandle (out), + parent, + &newstdout, + 0, + TRUE, + DUPLICATE_SAME_ACCESS)) + report_file_error ("Duplicating output handle for child", Qnil); - 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)); - } + if (!DuplicateHandle (parent, + (HANDLE) _get_osfhandle (err), + parent, + &newstderr, + 0, + TRUE, + DUPLICATE_SAME_ACCESS)) + report_file_error ("Duplicating error handle for child", Qnil); + + /* and store them as our std handles */ + if (!SetStdHandle (STD_INPUT_HANDLE, newstdin)) + report_file_error ("Changing stdin handle", Qnil); - return nchars; + if (!SetStdHandle (STD_OUTPUT_HANDLE, newstdout)) + report_file_error ("Changing stdout handle", Qnil); + + if (!SetStdHandle (STD_ERROR_HANDLE, newstderr)) + report_file_error ("Changing stderr handle", Qnil); +} + +void +reset_standard_handles (int in, int out, int err, HANDLE handles[3]) +{ + /* close the duplicated handles passed to the child */ + CloseHandle (GetStdHandle (STD_INPUT_HANDLE)); + CloseHandle (GetStdHandle (STD_OUTPUT_HANDLE)); + CloseHandle (GetStdHandle (STD_ERROR_HANDLE)); + + /* now restore parent's saved std handles */ + SetStdHandle (STD_INPUT_HANDLE, handles[0]); + SetStdHandle (STD_OUTPUT_HANDLE, handles[1]); + SetStdHandle (STD_ERROR_HANDLE, handles[2]); } + +/* end of ntproc.c */ |