From c519b5e10e0706332a557a49299b96537305d459 Mon Sep 17 00:00:00 2001 From: Geoff Voelker Date: Fri, 3 May 1996 18:35:50 +0000 Subject: Include config.h after CRT headers. Include fcntl.h. (child_process, MAX_CHILDREN, CHILD_ACTIVE): Moved to nt.h. (DebugPrint): New macro. (new_child): Create input event structures for child processes. (delete_child): New function. (reader_thread): Return nonzero upon failure. Use _sys_read_ahead. (create_child): Add child_process and parent PID arguments. Don't create input event or thread structures here. Fixup Win95 negative process IDs. (register_child): Don't set consumed event; instead, set thread status so that select will release it. (remove_child): Renamed to reap_process. Only free resources of synchronous children. (win32_wait): Renamed to sys_wait. Sanity check to make sure handle to subprocess exists. Don't reclaim thread resources here. (win32_spawnve): Renamed to sys_spawnve. Check for proper wait mode first thing. Convert command name to DOS format. Quote whitespace and quotes in arguments to subprocess. Use alloca instead of malloc. If max subprocesses, return EAGAIN in hopes of another completing. (sys_select): Add support for socket input. Remove dead code. Check input from children fairly. (sys_select) [HAVE_TIMEVAL]: Remove conditional check and code. (win32_kill_process): Renamed to sys_kill. Use OpenProcess to terminate process. (prepate_standard_handles, reset_standard_handles): Moved here from nt.c. --- src/w32proc.c | 869 +++++++++++++++++++++++++++++++++++----------------------- 1 file changed, 523 insertions(+), 346 deletions(-) (limited to 'src') 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 - #include #include #include #include +#include #include +/* must include CRT headers *before* config.h */ +#include "config.h" +#undef signal +#undef wait +#undef spawnve +#undef select +#undef kill + #include #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 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 */ -- cgit v1.2.1