X-Git-Url: https://code.delx.au/gnu-emacs/blobdiff_plain/4fc35edd5fcdfe258c04cfed707753fdd8795a72..ee80c1170916aae1bca62df5ca086fcc13486de1:/src/w32proc.c diff --git a/src/w32proc.c b/src/w32proc.c index 74731db242..1f633d834c 100644 --- a/src/w32proc.c +++ b/src/w32proc.c @@ -786,6 +786,138 @@ alarm (int seconds) #endif } + + +/* Here's an overview of how support for subprocesses and + network/serial streams is implemented on MS-Windows. + + The management of both subprocesses and network/serial streams + circles around the child_procs[] array, which can record up to the + grand total of MAX_CHILDREN (= 32) of these. (The reasons for the + 32 limitation will become clear below.) Each member of + child_procs[] is a child_process structure, defined on w32.h. + + A related data structure is the fd_info[] array, which holds twice + as many members, 64, and records the information about file + descriptors used for communicating with subprocesses and + network/serial devices. Each member of the array is the filedesc + structure, which records the Windows handle for communications, + such as the read end of the pipe to a subprocess, a socket handle, + etc. + + Both these arrays reference each other: there's a member of + child_process structure that records the corresponding file + descriptor, and there's a member of filedesc structure that holds a + pointer to the corresponding child_process. + + Whenever Emacs starts a subprocess or opens a network/serial + stream, the function new_child is called to prepare a new + child_process structure. new_child looks for the first vacant slot + in the child_procs[] array, initializes it, and starts a "reader + thread" that will watch the output of the subprocess/stream and its + status. (If no vacant slot can be found, new_child returns a + failure indication to its caller, and the higher-level Emacs + primitive that called it will then fail with EMFILE or EAGAIN.) + + The reader thread started by new_child communicates with the main + (a.k.a. "Lisp") thread via two event objects and a status, all of + them recorded by the members of the child_process structure in + child_procs[]. The event objects serve as semaphores between the + reader thread and the 'pselect' emulation in sys_select, as follows: + + . Initially, the reader thread is waiting for the char_consumed + event to become signaled by sys_select, which is an indication + for the reader thread to go ahead and try reading more stuff + from the subprocess/stream. + + . The reader thread then attempts to read by calling a + blocking-read function. When the read call returns, either + successfully or with some failure indication, the reader thread + updates the status of the read accordingly, and signals the 2nd + event object, char_avail, on whose handle sys_select is + waiting. This tells sys_select that the file descriptor + allocated for the subprocess or the the stream is ready to be + read from. + + When the subprocess exits or the network/serial stream is closed, + the reader thread sets the status accordingly and exits. It also + exits when the main thread sets the status to STATUS_READ_ERROR + and/or the char_avail and char_consumed event handles become NULL; + this is how delete_child, called by Emacs when a subprocess or a + stream is terminated, terminates the reader thread as part of + deleting the child_process object. + + The sys_select function emulates the Posix 'pselect' function; it + is needed because the Windows 'select' function supports only + network sockets, while Emacs expects 'pselect' to work for any file + descriptor, including pipes and serial streams. + + When sys_select is called, it uses the information in fd_info[] + array to convert the file descriptors which it was asked to watch + into Windows handles. In general, the handle to watch is the + handle of the char_avail event of the child_process structure that + corresponds to the file descriptor. In addition, for subprocesses, + sys_select watches one more handle: the handle for the subprocess, + so that it could emulate the SIGCHLD signal when the subprocess + exits. + + If file descriptor zero (stdin) doesn't have its bit set in the + 'rfds' argument to sys_select, the function always watches for + keyboard interrupts, to be able to interrupt the wait and return + when the user presses C-g. + + Having collected the handles to watch, sys_select calls + WaitForMultipleObjects to wait for any one of them to become + signaled. Since WaitForMultipleObjects can only watch up to 64 + handles, Emacs on Windows is limited to maximum 32 child_process + objects (since a subprocess consumes 2 handles to be watched, see + above). + + When any of the handles become signaled, sys_select does whatever + is appropriate for the corresponding child_process object: + + . If it's a handle to the char_avail event, sys_select marks the + corresponding bit in 'rfds', and Emacs will then read from that + file descriptor. + + . If it's a handle to the process, sys_select calls the SIGCHLD + handler, to inform Emacs of the fact that the subprocess + exited. + + The waitpid emulation works very similar to sys_select, except that + it only watches handles of subprocesses, and doesn't synchronize + with the reader thread. + + Because socket descriptors on Windows are handles, while Emacs + expects them to be file descriptors, all low-level I/O functions, + such as 'read' and 'write', and all socket operations, like + 'connect', 'recvfrom', 'accept', etc., are redirected to the + corresponding 'sys_*' functions, which must convert a file + descriptor to a handle using the fd_info[] array, and then invoke + the corresponding Windows API on the handle. Most of these + redirected 'sys_*' functions are implemented on w32.c. + + When the file descriptor was produced by functions such as 'open', + the corresponding handle is obtained by calling _get_osfhandle. To + produce a file descriptor for a socket handle, which has no file + descriptor as far as Windows is concerned, the function + socket_to_fd opens the null device; the resulting file descriptor + will never be used directly in any I/O API, but serves as an index + into the fd_info[] array, where the socket handle is stored. The + SOCK_HANDLE macro retrieves the handle when given the file + descriptor. + + The function sys_kill emulates the Posix 'kill' functionality to + terminate other processes. It does that by attaching to the + foreground window of the process and sending a Ctrl-C or Ctrl-BREAK + signal to the process; if that doesn't work, then it calls + TerminateProcess to forcibly terminate the process. Note that this + only terminates the immediate process whose PID was passed to + sys_kill; it doesn't terminate the child processes of that process. + This means, for example, that an Emacs subprocess run through a + shell might not be killed, because sys_kill will only terminate the + shell. (In practice, however, such problems are very rare.) */ + /* Defined in which conflicts with the local copy */ #define _P_NOWAIT 1 @@ -1011,7 +1143,9 @@ reader_thread (void *arg) { int rc; - if (cp->fd >= 0 && fd_info[cp->fd].flags & FILE_LISTEN) + if (cp->fd >= 0 && (fd_info[cp->fd].flags & FILE_CONNECT) != 0) + rc = _sys_wait_connect (cp->fd); + else if (cp->fd >= 0 && (fd_info[cp->fd].flags & FILE_LISTEN) != 0) rc = _sys_wait_accept (cp->fd); else rc = _sys_read_ahead (cp->fd); @@ -1031,8 +1165,8 @@ reader_thread (void *arg) return 1; } - if (rc == STATUS_READ_ERROR) - return 1; + if (rc == STATUS_READ_ERROR || rc == STATUS_CONNECT_FAILED) + return 2; /* If the read died, the child has died so let the thread die */ if (rc == STATUS_READ_FAILED) @@ -1479,24 +1613,25 @@ w32_executable_type (char * filename, #endif if (data_dir) { - /* Look for cygwin.dll in DLL import list. */ + /* Look for Cygwin DLL in the DLL import list. */ IMAGE_DATA_DIRECTORY import_dir = data_dir[IMAGE_DIRECTORY_ENTRY_IMPORT]; - IMAGE_IMPORT_DESCRIPTOR * imports; - IMAGE_SECTION_HEADER * section; - - section = rva_to_section (import_dir.VirtualAddress, nt_header); - imports = RVA_TO_PTR (import_dir.VirtualAddress, section, - executable); + IMAGE_IMPORT_DESCRIPTOR * imports = + RVA_TO_PTR (import_dir.VirtualAddress, + rva_to_section (import_dir.VirtualAddress, + nt_header), + executable); for ( ; imports->Name; imports++) { + IMAGE_SECTION_HEADER * section = + rva_to_section (imports->Name, nt_header); char * dllname = RVA_TO_PTR (imports->Name, section, executable); - /* The exact name of the cygwin dll has changed with - various releases, but hopefully this will be reasonably - future proof. */ + /* The exact name of the Cygwin DLL has changed with + various releases, but hopefully this will be + reasonably future-proof. */ if (strncmp (dllname, "cygwin", 6) == 0) { *is_cygnus_app = TRUE; @@ -1929,7 +2064,7 @@ int sys_select (int nfds, SELECT_TYPE *rfds, SELECT_TYPE *wfds, SELECT_TYPE *efds, struct timespec *timeout, void *ignored) { - SELECT_TYPE orfds; + SELECT_TYPE orfds, owfds; DWORD timeout_ms, start_time; int i, nh, nc, nr; DWORD active; @@ -1947,15 +2082,27 @@ sys_select (int nfds, SELECT_TYPE *rfds, SELECT_TYPE *wfds, SELECT_TYPE *efds, return 0; } - /* Otherwise, we only handle rfds, so fail otherwise. */ - if (rfds == NULL || wfds != NULL || efds != NULL) + /* Otherwise, we only handle rfds and wfds, so fail otherwise. */ + if ((rfds == NULL && wfds == NULL) || efds != NULL) { errno = EINVAL; return -1; } - orfds = *rfds; - FD_ZERO (rfds); + if (rfds) + { + orfds = *rfds; + FD_ZERO (rfds); + } + else + FD_ZERO (&orfds); + if (wfds) + { + owfds = *wfds; + FD_ZERO (wfds); + } + else + FD_ZERO (&owfds); nr = 0; /* If interrupt_handle is available and valid, always wait on it, to @@ -1970,7 +2117,7 @@ sys_select (int nfds, SELECT_TYPE *rfds, SELECT_TYPE *wfds, SELECT_TYPE *efds, /* Build a list of pipe handles to wait on. */ for (i = 0; i < nfds; i++) - if (FD_ISSET (i, &orfds)) + if (FD_ISSET (i, &orfds) || FD_ISSET (i, &owfds)) { if (i == 0) { @@ -1984,7 +2131,7 @@ sys_select (int nfds, SELECT_TYPE *rfds, SELECT_TYPE *wfds, SELECT_TYPE *efds, /* Check for any emacs-generated input in the queue since it won't be detected in the wait */ - if (detect_input_pending ()) + if (rfds && detect_input_pending ()) { FD_SET (i, rfds); return 1; @@ -1999,6 +2146,13 @@ sys_select (int nfds, SELECT_TYPE *rfds, SELECT_TYPE *wfds, SELECT_TYPE *efds, { /* Child process and socket/comm port input. */ cp = fd_info[i].cp; + if (FD_ISSET (i, &owfds) + && cp + && (fd_info[i].flags && FILE_CONNECT) == 0) + { + DebPrint (("sys_select: fd %d is in wfds, but FILE_CONNECT is reset!\n", i)); + cp = NULL; + } if (cp) { int current_status = cp->status; @@ -2007,6 +2161,8 @@ sys_select (int nfds, SELECT_TYPE *rfds, SELECT_TYPE *wfds, SELECT_TYPE *efds, { /* Tell reader thread which file handle to use. */ cp->fd = i; + /* Zero out the error code. */ + cp->errcode = 0; /* Wake up the reader thread for this process */ cp->status = STATUS_READ_READY; if (!SetEvent (cp->char_consumed)) @@ -2197,7 +2353,7 @@ count_children: if (cp->fd >= 0 && (fd_info[cp->fd].flags & FILE_AT_EOF) == 0) fd_info[cp->fd].flags |= FILE_SEND_SIGCHLD; - /* SIG_DFL for SIGCHLD is ignore */ + /* SIG_DFL for SIGCHLD is ignored */ else if (sig_handlers[SIGCHLD] != SIG_DFL && sig_handlers[SIGCHLD] != SIG_IGN) { @@ -2214,7 +2370,7 @@ count_children: errno = EINTR; return -1; } - else if (fdindex[active] == 0) + else if (rfds && fdindex[active] == 0) { /* Keyboard input available */ FD_SET (0, rfds); @@ -2222,9 +2378,33 @@ count_children: } else { - /* must be a socket or pipe - read ahead should have - completed, either succeeding or failing. */ - FD_SET (fdindex[active], rfds); + /* Must be a socket or pipe - read ahead should have + completed, either succeeding or failing. If this handle + was waiting for an async 'connect', reset the connect + flag, so it could read from now on. */ + if (wfds && (fd_info[fdindex[active]].flags & FILE_CONNECT) != 0) + { + cp = fd_info[fdindex[active]].cp; + if (cp) + { + /* Don't reset the FILE_CONNECT bit and don't + acknowledge the read if the status is + STATUS_CONNECT_FAILED or some other + failure. That's because the thread exits in those + cases, so it doesn't need the ACK, and we want to + keep the FILE_CONNECT bit as evidence that the + connect failed, to be checked in sys_read. */ + if (cp->status == STATUS_READ_SUCCEEDED) + { + fd_info[cp->fd].flags &= ~FILE_CONNECT; + cp->status = STATUS_READ_ACKNOWLEDGED; + } + ResetEvent (cp->char_avail); + } + FD_SET (fdindex[active], wfds); + } + else if (rfds) + FD_SET (fdindex[active], rfds); nr++; }