Welcome to mirror list, hosted at ThFree Co, Russian Federation.

cygwin.com/git/newlib-cygwin.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
Diffstat (limited to 'winsup/cygwin/fhandler/tty.cc')
-rw-r--r--winsup/cygwin/fhandler/tty.cc4272
1 files changed, 4272 insertions, 0 deletions
diff --git a/winsup/cygwin/fhandler/tty.cc b/winsup/cygwin/fhandler/tty.cc
new file mode 100644
index 000000000..0f9dec570
--- /dev/null
+++ b/winsup/cygwin/fhandler/tty.cc
@@ -0,0 +1,4272 @@
+/* fhandler_tty.cc
+
+This file is part of Cygwin.
+
+This software is a copyrighted work licensed under the terms of the
+Cygwin license. Please consult the file "CYGWIN_LICENSE" for
+details. */
+
+#include "winsup.h"
+#include <stdlib.h>
+#include <sys/param.h>
+#include <cygwin/acl.h>
+#include <cygwin/kd.h>
+#include "cygerrno.h"
+#include "security.h"
+#include "path.h"
+#include "fhandler.h"
+#include "dtable.h"
+#include "sigproc.h"
+#include "pinfo.h"
+#include "ntdll.h"
+#include "cygheap.h"
+#include "shared_info.h"
+#include "cygthread.h"
+#include "child_info.h"
+#include <asm/socket.h>
+#include "cygwait.h"
+#include "registry.h"
+#include "tls_pbuf.h"
+#include "winf.h"
+
+#ifndef PROC_THREAD_ATTRIBUTE_PSEUDOCONSOLE
+#define PROC_THREAD_ATTRIBUTE_PSEUDOCONSOLE 0x00020016
+#endif /* PROC_THREAD_ATTRIBUTE_PSEUDOCONSOLE */
+
+extern "C" int sscanf (const char *, const char *, ...);
+
+#define close_maybe(h) \
+ do { \
+ if (h && h != INVALID_HANDLE_VALUE) \
+ CloseHandle (h); \
+ } while (0)
+
+/* pty master control pipe messages */
+struct pipe_request {
+ DWORD pid;
+};
+
+/* The name *nat* comes from 'native' which means non-cygwin
+ (native windows). They are used for non-cygwin process. */
+struct pipe_reply {
+ HANDLE from_master_nat;
+ HANDLE from_master;
+ HANDLE to_master_nat;
+ HANDLE to_master;
+ HANDLE to_slave_nat;
+ HANDLE to_slave;
+ DWORD error;
+};
+
+HANDLE attach_mutex;
+
+DWORD acquire_attach_mutex (DWORD t)
+{
+ if (!attach_mutex)
+ return WAIT_OBJECT_0;
+ return WaitForSingleObject (attach_mutex, t);
+}
+
+void release_attach_mutex (void)
+{
+ if (!attach_mutex)
+ return;
+ ReleaseMutex (attach_mutex);
+}
+
+inline static bool process_alive (DWORD pid);
+
+/* This functions looks for a process which attached to the same console
+ with current process and is matched to given conditions:
+ match: If true, return given pid if the process pid attaches to the
+ same console, otherwise, return 0. If false, return pid except
+ for given pid.
+ cygwin: return only process's pid which has cygwin pid.
+ stub_only: return only stub process's pid of non-cygwin process. */
+DWORD
+fhandler_pty_common::get_console_process_id (DWORD pid, bool match,
+ bool cygwin, bool stub_only)
+{
+ tmp_pathbuf tp;
+ DWORD *list = (DWORD *) tp.c_get ();
+ const DWORD buf_size = NT_MAX_PATH / sizeof (DWORD);
+
+ DWORD num = GetConsoleProcessList (list, buf_size);
+ if (num == 0 || num > buf_size)
+ return 0;
+
+ DWORD res_pri = 0, res = 0;
+ /* Last one is the oldest. */
+ /* https://github.com/microsoft/terminal/issues/95 */
+ for (int i = (int) num - 1; i >= 0; i--)
+ if ((match && list[i] == pid) || (!match && list[i] != pid))
+ {
+ if (!cygwin)
+ {
+ res_pri = list[i];
+ break;
+ }
+ else
+ {
+ pinfo p (cygwin_pid (list[i]));
+ if (!!p && p->exec_dwProcessId)
+ {
+ res_pri = stub_only ? p->exec_dwProcessId : list[i];
+ break;
+ }
+ if (!p && !res && process_alive (list[i]) && stub_only)
+ res = list[i];
+ if (!!p && !res && !stub_only)
+ res = list[i];
+ }
+ }
+ return res_pri ?: res;
+}
+
+static bool isHybrid; /* Set true if the active pipe is set to nat pipe
+ owned by myself even though the current process
+ is a cygwin process. */
+static HANDLE h_gdb_inferior; /* Handle of GDB inferior process. */
+
+static void
+set_switch_to_nat_pipe (HANDLE *in, HANDLE *out, HANDLE *err)
+{
+ cygheap_fdenum cfd (false);
+ int fd;
+ fhandler_base *replace_in = NULL, *replace_out = NULL, *replace_err = NULL;
+ fhandler_pty_slave *ptys = NULL;
+ while ((fd = cfd.next ()) >= 0)
+ {
+ if (*in == cfd->get_handle () ||
+ (fd == 0 && *in == GetStdHandle (STD_INPUT_HANDLE)))
+ replace_in = (fhandler_base *) cfd;
+ if (*out == cfd->get_output_handle () ||
+ (fd == 1 && *out == GetStdHandle (STD_OUTPUT_HANDLE)))
+ replace_out = (fhandler_base *) cfd;
+ if (*err == cfd->get_output_handle () ||
+ (fd == 2 && *err == GetStdHandle (STD_ERROR_HANDLE)))
+ replace_err = (fhandler_base *) cfd;
+ if (cfd->get_device () == (dev_t) myself->ctty)
+ {
+ fhandler_base *fh = cfd;
+ if (*in == fh->get_handle ()
+ || *out == fh->get_output_handle ()
+ || *err == fh->get_output_handle ())
+ ptys = (fhandler_pty_slave *) fh;
+ }
+ }
+ if (ptys)
+ ptys->set_switch_to_nat_pipe ();
+ if (replace_in)
+ *in = replace_in->get_handle_nat ();
+ if (replace_out)
+ *out = replace_out->get_output_handle_nat ();
+ if (replace_err)
+ *err = replace_err->get_output_handle_nat ();
+}
+
+/* Determine if the given path is cygwin binary. */
+static bool
+path_iscygexec_a_w (LPCSTR na, LPSTR ca, LPCWSTR nw, LPWSTR cw)
+{
+ path_conv path;
+ tmp_pathbuf tp;
+ char *prog =tp.c_get ();
+ if (na)
+ {
+ __small_sprintf (prog, "%s", na);
+ find_exec (prog, path);
+ }
+ else if (nw)
+ {
+ __small_sprintf (prog, "%W", nw);
+ find_exec (prog, path);
+ }
+ else
+ {
+ if (ca)
+ __small_sprintf (prog, "%s", ca);
+ else if (cw)
+ __small_sprintf (prog, "%W", cw);
+ else
+ return true;
+ char *p = prog;
+ char *p1;
+ do
+ if ((p1 = strchr (p, ' ')) || (p1 = p + strlen (p)))
+ {
+ p = p1;
+ if (*p == ' ')
+ {
+ *p = '\0';
+ find_exec (prog, path);
+ *p = ' ';
+ p ++;
+ }
+ else if (*p == '\0')
+ find_exec (prog, path);
+ }
+ while (!path.exists() && *p);
+ }
+ const char *argv[] = {"", NULL}; /* Dummy */
+ av av1;
+ av1.setup ("", path, "", 1, argv, false);
+ return path.iscygexec ();
+}
+
+bool
+fhandler_termios::path_iscygexec_a (LPCSTR n, LPSTR c)
+{
+ return path_iscygexec_a_w (n, c, NULL, NULL);
+}
+
+bool
+fhandler_termios::path_iscygexec_w (LPCWSTR n, LPWSTR c)
+{
+ return path_iscygexec_a_w (NULL, NULL, n, c);
+}
+
+static bool atexit_func_registered = false;
+static bool debug_process = false;
+
+static void
+atexit_func (void)
+{
+ if (isHybrid)
+ {
+ cygheap_fdenum cfd (false);
+ while (cfd.next () >= 0)
+ if (cfd->get_device () == (dev_t) myself->ctty)
+ {
+ DWORD force_switch_to = 0;
+ if (WaitForSingleObject (h_gdb_inferior, 0) == WAIT_TIMEOUT
+ && !debug_process)
+ force_switch_to = GetProcessId (h_gdb_inferior);
+ fhandler_base *fh = cfd;
+ fhandler_pty_slave *ptys = (fhandler_pty_slave *) fh;
+ tty *ttyp = (tty *) ptys->tc ();
+ bool stdin_is_ptys =
+ GetStdHandle (STD_INPUT_HANDLE) == ptys->get_handle ();
+ struct fhandler_pty_slave::handle_set_t handles =
+ {
+ ptys->get_handle_nat (),
+ ptys->get_input_available_event (),
+ ptys->input_mutex,
+ ptys->pipe_sw_mutex
+ };
+ fhandler_pty_slave::cleanup_for_non_cygwin_app (&handles, ttyp,
+ stdin_is_ptys,
+ force_switch_to);
+ break;
+ }
+ CloseHandle (h_gdb_inferior);
+ }
+}
+
+#define DEF_HOOK(name) static __typeof__ (name) *name##_Orig
+/* CreateProcess() is hooked for GDB etc. */
+DEF_HOOK (CreateProcessA);
+DEF_HOOK (CreateProcessW);
+
+static BOOL
+CreateProcessA_Hooked
+ (LPCSTR n, LPSTR c, LPSECURITY_ATTRIBUTES pa, LPSECURITY_ATTRIBUTES ta,
+ BOOL inh, DWORD f, LPVOID e, LPCSTR d,
+ LPSTARTUPINFOA si, LPPROCESS_INFORMATION pi)
+{
+ STARTUPINFOEXA siex = {0, };
+ if (si->cb == sizeof (STARTUPINFOEXA))
+ siex = *(STARTUPINFOEXA *)si;
+ else
+ siex.StartupInfo = *si;
+ STARTUPINFOA *siov = &siex.StartupInfo;
+ if (!(si->dwFlags & STARTF_USESTDHANDLES))
+ {
+ siov->dwFlags |= STARTF_USESTDHANDLES;
+ siov->hStdInput = GetStdHandle (STD_INPUT_HANDLE);
+ siov->hStdOutput = GetStdHandle (STD_OUTPUT_HANDLE);
+ siov->hStdError = GetStdHandle (STD_ERROR_HANDLE);
+ }
+ bool path_iscygexec = fhandler_termios::path_iscygexec_a (n, c);
+ if (!path_iscygexec)
+ set_switch_to_nat_pipe (&siov->hStdInput, &siov->hStdOutput,
+ &siov->hStdError);
+ BOOL ret = CreateProcessA_Orig (n, c, pa, ta, inh, f, e, d, siov, pi);
+ h_gdb_inferior = pi->hProcess;
+ DuplicateHandle (GetCurrentProcess (), h_gdb_inferior,
+ GetCurrentProcess (), &h_gdb_inferior,
+ 0, 0, DUPLICATE_SAME_ACCESS);
+ debug_process = !!(f & (DEBUG_PROCESS | DEBUG_ONLY_THIS_PROCESS));
+ if (debug_process)
+ mutex_timeout = 0; /* to avoid deadlock in GDB */
+ if (!atexit_func_registered && !path_iscygexec)
+ {
+ atexit (atexit_func);
+ atexit_func_registered = true;
+ }
+ return ret;
+}
+
+static BOOL
+CreateProcessW_Hooked
+ (LPCWSTR n, LPWSTR c, LPSECURITY_ATTRIBUTES pa, LPSECURITY_ATTRIBUTES ta,
+ BOOL inh, DWORD f, LPVOID e, LPCWSTR d,
+ LPSTARTUPINFOW si, LPPROCESS_INFORMATION pi)
+{
+ STARTUPINFOEXW siex = {0, };
+ if (si->cb == sizeof (STARTUPINFOEXW))
+ siex = *(STARTUPINFOEXW *)si;
+ else
+ siex.StartupInfo = *si;
+ STARTUPINFOW *siov = &siex.StartupInfo;
+ if (!(si->dwFlags & STARTF_USESTDHANDLES))
+ {
+ siov->dwFlags |= STARTF_USESTDHANDLES;
+ siov->hStdInput = GetStdHandle (STD_INPUT_HANDLE);
+ siov->hStdOutput = GetStdHandle (STD_OUTPUT_HANDLE);
+ siov->hStdError = GetStdHandle (STD_ERROR_HANDLE);
+ }
+ bool path_iscygexec = fhandler_termios::path_iscygexec_w (n, c);
+ if (!path_iscygexec)
+ set_switch_to_nat_pipe (&siov->hStdInput, &siov->hStdOutput,
+ &siov->hStdError);
+ BOOL ret = CreateProcessW_Orig (n, c, pa, ta, inh, f, e, d, siov, pi);
+ h_gdb_inferior = pi->hProcess;
+ DuplicateHandle (GetCurrentProcess (), h_gdb_inferior,
+ GetCurrentProcess (), &h_gdb_inferior,
+ 0, 0, DUPLICATE_SAME_ACCESS);
+ debug_process = !!(f & (DEBUG_PROCESS | DEBUG_ONLY_THIS_PROCESS));
+ if (debug_process)
+ mutex_timeout = 0; /* to avoid deadlock in GDB */
+ if (!atexit_func_registered && !path_iscygexec)
+ {
+ atexit (atexit_func);
+ atexit_func_registered = true;
+ }
+ return ret;
+}
+
+static void
+convert_mb_str (UINT cp_to, char *ptr_to, size_t *len_to,
+ UINT cp_from, const char *ptr_from, size_t len_from,
+ mbstate_t *mbp)
+{
+ tmp_pathbuf tp;
+ wchar_t *wbuf = tp.w_get ();
+ int wlen = 0;
+ char *tmpbuf = tp.c_get ();
+ memcpy (tmpbuf, mbp->__value.__wchb, mbp->__count);
+ if (mbp->__count + len_from > NT_MAX_PATH)
+ len_from = NT_MAX_PATH - mbp->__count;
+ memcpy (tmpbuf + mbp->__count, ptr_from, len_from);
+ int total_len = mbp->__count + len_from;
+ mbp->__count = 0;
+ int mblen = 0;
+ for (const char *p = tmpbuf; p < tmpbuf + total_len; p += mblen)
+ /* Max bytes in multibyte char supported is 4. */
+ for (mblen = 1; mblen <= 4; mblen ++)
+ {
+ /* Try conversion */
+ int l = MultiByteToWideChar (cp_from, MB_ERR_INVALID_CHARS,
+ p, mblen,
+ wbuf + wlen, NT_MAX_PATH - wlen);
+ if (l)
+ { /* Conversion Success */
+ wlen += l;
+ break;
+ }
+ else if (mblen == 4)
+ { /* Conversion Fail */
+ l = MultiByteToWideChar (cp_from, 0, p, 1,
+ wbuf + wlen, NT_MAX_PATH - wlen);
+ wlen += l;
+ mblen = 1;
+ break;
+ }
+ else if (p + mblen == tmpbuf + total_len)
+ { /* Multibyte char incomplete */
+ memcpy (mbp->__value.__wchb, p, mblen);
+ mbp->__count = mblen;
+ break;
+ }
+ /* Retry conversion with extended length */
+ }
+ *len_to = WideCharToMultiByte (cp_to, 0, wbuf, wlen,
+ ptr_to, *len_to, NULL, NULL);
+}
+
+static bool
+bytes_available (DWORD& n, HANDLE h)
+{
+ DWORD navail, nleft;
+ navail = nleft = 0;
+ bool succeeded = PeekNamedPipe (h, NULL, 0, NULL, &navail, &nleft);
+ if (succeeded)
+ /* nleft should always be the right choice unless something has written 0
+ bytes to the pipe. In that pathological case we return the actual number
+ of bytes available in the pipe. See cgf-000008 for more details. */
+ n = nleft ?: navail;
+ else
+ {
+ termios_printf ("PeekNamedPipe(%p) failed, %E", h);
+ n = 0;
+ }
+ debug_only_printf ("n %u, nleft %u, navail %u", n, nleft, navail);
+ return succeeded;
+}
+
+bool
+fhandler_pty_common::bytes_available (DWORD &n)
+{
+ return ::bytes_available (n, get_handle ());
+}
+
+#ifdef DEBUGGING
+static class mutex_stack
+{
+public:
+ const char *fn;
+ int ln;
+ const char *tname;
+} ostack[100];
+
+static int osi;
+#endif /*DEBUGGING*/
+
+void
+fhandler_pty_master::flush_to_slave ()
+{
+ if (get_readahead_valid () && !(get_ttyp ()->ti.c_lflag & ICANON))
+ accept_input ();
+}
+
+void
+fhandler_pty_master::discard_input ()
+{
+ DWORD bytes_in_pipe;
+ char buf[1024];
+ DWORD n;
+
+ WaitForSingleObject (input_mutex, mutex_timeout);
+ while (::bytes_available (bytes_in_pipe, from_master) && bytes_in_pipe)
+ ReadFile (from_master, buf, sizeof(buf), &n, NULL);
+ ResetEvent (input_available_event);
+ if (!get_ttyp ()->pcon_activated)
+ while (::bytes_available (bytes_in_pipe, from_master_nat) && bytes_in_pipe)
+ ReadFile (from_master_nat, buf, sizeof(buf), &n, NULL);
+ get_ttyp ()->discard_input = true;
+ ReleaseMutex (input_mutex);
+}
+
+DWORD
+fhandler_pty_common::__acquire_output_mutex (const char *fn, int ln,
+ DWORD ms)
+{
+ if (strace.active ())
+ strace.prntf (_STRACE_TERMIOS, fn, "(%d): pty output_mutex (%p): waiting %d ms", ln, output_mutex, ms);
+ DWORD res = WaitForSingleObject (output_mutex, ms);
+ if (res == WAIT_OBJECT_0)
+ {
+#ifndef DEBUGGING
+ if (strace.active ())
+ strace.prntf (_STRACE_TERMIOS, fn, "(%d): pty output_mutex: acquired", ln, res);
+#else
+ ostack[osi].fn = fn;
+ ostack[osi].ln = ln;
+ ostack[osi].tname = mythreadname ();
+ termios_printf ("acquired for %s:%d, osi %d", fn, ln, osi);
+ osi++;
+#endif
+ }
+ return res;
+}
+
+void
+fhandler_pty_common::__release_output_mutex (const char *fn, int ln)
+{
+ if (ReleaseMutex (output_mutex))
+ {
+#ifndef DEBUGGING
+ if (strace.active ())
+ strace.prntf (_STRACE_TERMIOS, fn, "(%d): pty output_mutex(%p) released", ln, output_mutex);
+#else
+ if (osi > 0)
+ osi--;
+ termios_printf ("released(%p) at %s:%d, osi %d", output_mutex, fn, ln, osi);
+ termios_printf (" for %s:%d (%s)", ostack[osi].fn, ostack[osi].ln, ostack[osi].tname);
+ ostack[osi].ln = -ln;
+#endif
+ }
+#ifdef DEBUGGING
+ else if (osi > 0)
+ {
+ system_printf ("couldn't release output mutex but we seem to own it, %E");
+ try_to_debug ();
+ }
+#endif
+}
+
+/* Process pty input. */
+
+void
+fhandler_pty_master::doecho (const void *str, DWORD len)
+{
+ ssize_t towrite = len;
+ if (!process_opost_output (echo_w, str, towrite, true,
+ get_ttyp (), is_nonblocking ()))
+ termios_printf ("Write to echo pipe failed, %E");
+}
+
+int
+fhandler_pty_master::accept_input ()
+{
+ DWORD bytes_left;
+ int ret = 1;
+
+ WaitForSingleObject (input_mutex, mutex_timeout);
+
+ char *p = rabuf () + raixget ();
+ bytes_left = eat_readahead (-1);
+
+ HANDLE write_to = get_output_handle ();
+ tmp_pathbuf tp;
+ if (to_be_read_from_nat_pipe ()
+ && get_ttyp ()->pty_input_state == tty::to_nat)
+ {
+ /* This code is reached if non-cygwin app is foreground and
+ pseudo console is not enabled. */
+ write_to = to_slave_nat; /* write to nat pipe rather than cyg pipe. */
+
+ /* Charset conversion for non-cygwin apps. */
+ UINT cp_to;
+ pinfo pinfo_target = pinfo (get_ttyp ()->invisible_console_pid);
+ DWORD target_pid = 0;
+ if (pinfo_target)
+ target_pid = pinfo_target->dwProcessId;
+ if (target_pid)
+ {
+ /* Slave attaches to a different console than master.
+ Therefore reattach here. */
+ DWORD resume_pid =
+ attach_console_temporarily (target_pid, helper_pid);
+ cp_to = GetConsoleCP ();
+ resume_from_temporarily_attach (resume_pid);
+ }
+ else
+ cp_to = GetConsoleCP ();
+
+ if (get_ttyp ()->term_code_page != cp_to)
+ {
+ static mbstate_t mbp;
+ char *mbbuf = tp.c_get ();
+ size_t nlen = NT_MAX_PATH;
+ convert_mb_str (cp_to, mbbuf, &nlen,
+ get_ttyp ()->term_code_page, p, bytes_left, &mbp);
+ p = mbbuf;
+ bytes_left = nlen;
+ }
+ }
+
+ if (!bytes_left)
+ {
+ termios_printf ("sending EOF to slave");
+ get_ttyp ()->read_retval = 0;
+ }
+ else
+ {
+ BOOL rc = TRUE;
+ DWORD written = 0;
+
+ paranoid_printf ("about to write %u chars to slave", bytes_left);
+ /* Write line by line for transfer input. */
+ char *p0 = p;
+ char *p_cr = (char *) memchr (p0, '\r', bytes_left - (p0 - p));
+ char *p_lf = (char *) memchr (p0, '\n', bytes_left - (p0 - p));
+ DWORD n;
+ while (p_cr || p_lf)
+ {
+ char *p1 =
+ p_cr ? (p_lf ? ((p_cr + 1 == p_lf)
+ ? p_lf : min(p_cr, p_lf)) : p_cr) : p_lf;
+ n = p1 - p0 + 1;
+ rc = WriteFile (write_to, p0, n, &n, NULL);
+ if (rc)
+ written += n;
+ p0 = p1 + 1;
+ p_cr = (char *) memchr (p0, '\r', bytes_left - (p0 - p));
+ p_lf = (char *) memchr (p0, '\n', bytes_left - (p0 - p));
+ }
+ if (rc && (n = bytes_left - (p0 - p)))
+ {
+ rc = WriteFile (write_to, p0, n, &n, NULL);
+ if (rc)
+ written += n;
+ }
+ if (!rc && written == 0)
+ {
+ debug_printf ("error writing to pipe %p %E", write_to);
+ get_ttyp ()->read_retval = -1;
+ puts_readahead (p, bytes_left);
+ ret = -1;
+ }
+ else
+ {
+ get_ttyp ()->read_retval = 1;
+ p += written;
+ bytes_left -= written;
+ if (bytes_left > 0)
+ {
+ debug_printf ("to_slave pipe is full");
+ puts_readahead (p, bytes_left);
+ ret = 0;
+ }
+ }
+ }
+
+ if (write_to == get_output_handle ())
+ SetEvent (input_available_event); /* Set input_available_event only when
+ the data is written to cyg pipe. */
+ ReleaseMutex (input_mutex);
+ return ret;
+}
+
+bool
+fhandler_pty_master::hit_eof ()
+{
+ if (get_ttyp ()->was_opened && !get_ttyp ()->slave_alive ())
+ {
+ /* We have the only remaining open handle to this pty, and
+ the slave pty has been opened at least once. We treat
+ this as EOF. */
+ termios_printf ("all other handles closed");
+ return 1;
+ }
+ return 0;
+}
+
+/* Process pty output requests */
+
+int
+fhandler_pty_master::process_slave_output (char *buf, size_t len, int pktmode_on)
+{
+ size_t rlen;
+ char outbuf[OUT_BUFFER_SIZE];
+ DWORD n;
+ DWORD echo_cnt;
+ int rc = 0;
+
+ flush_to_slave ();
+
+ if (len == 0)
+ goto out;
+
+ for (;;)
+ {
+ n = echo_cnt = 0;
+ for (;;)
+ {
+ /* Check echo pipe first. */
+ if (::bytes_available (echo_cnt, echo_r) && echo_cnt > 0)
+ break;
+ if (!bytes_available (n))
+ goto err;
+ if (n)
+ break;
+ if (hit_eof ())
+ {
+ set_errno (EIO);
+ rc = -1;
+ goto out;
+ }
+ /* tclush can finish here. */
+ if (!buf)
+ goto out;
+
+ if (is_nonblocking ())
+ {
+ set_errno (EAGAIN);
+ rc = -1;
+ goto out;
+ }
+ pthread_testcancel ();
+ if (cygwait (NULL, 10, cw_sig_eintr) == WAIT_SIGNALED
+ && !_my_tls.call_signal_handler ())
+ {
+ set_errno (EINTR);
+ rc = -1;
+ goto out;
+ }
+ flush_to_slave ();
+ }
+
+ /* Set RLEN to the number of bytes to read from the pipe. */
+ rlen = len;
+
+ char *optr;
+ optr = buf;
+ if (pktmode_on && buf)
+ {
+ *optr++ = TIOCPKT_DATA;
+ rlen -= 1;
+ }
+
+ if (rlen == 0)
+ {
+ rc = optr - buf;
+ goto out;
+ }
+
+ if (rlen > sizeof outbuf)
+ rlen = sizeof outbuf;
+
+ /* If echo pipe has data (something has been typed or pasted), prefer
+ it over slave output. */
+ if (echo_cnt > 0)
+ {
+ if (!ReadFile (echo_r, outbuf, rlen, &n, NULL))
+ {
+ termios_printf ("ReadFile on echo pipe failed, %E");
+ goto err;
+ }
+ }
+ else if (!ReadFile (get_handle (), outbuf, rlen, &n, NULL))
+ {
+ termios_printf ("ReadFile failed, %E");
+ goto err;
+ }
+
+ termios_printf ("bytes read %u", n);
+
+ if (!buf || ((get_ttyp ()->ti.c_lflag & FLUSHO)
+ && !get_ttyp ()->mask_flusho))
+ continue; /* Discard read data */
+
+ memcpy (optr, outbuf, n);
+ optr += n;
+ rc = optr - buf;
+ break;
+
+ err:
+ if (GetLastError () == ERROR_BROKEN_PIPE)
+ rc = 0;
+ else
+ {
+ __seterrno ();
+ rc = -1;
+ }
+ break;
+ }
+
+out:
+ if (buf)
+ set_mask_flusho (false);
+ termios_printf ("returning %d", rc);
+ return rc;
+}
+
+/* pty slave stuff */
+
+fhandler_pty_slave::fhandler_pty_slave (int unit)
+ : fhandler_pty_common (), inuse (NULL), output_handle_nat (NULL),
+ io_handle_nat (NULL), slave_reading (NULL), num_reader (0)
+{
+ if (unit >= 0)
+ dev ().parse (DEV_PTYS_MAJOR, unit);
+}
+
+int
+fhandler_pty_slave::open (int flags, mode_t)
+{
+ HANDLE pty_owner;
+ HANDLE from_master_nat_local, from_master_local;
+ HANDLE to_master_nat_local, to_master_local;
+ HANDLE *handles[] =
+ {
+ &from_master_nat_local, &input_available_event, &input_mutex, &inuse,
+ &output_mutex, &to_master_nat_local, &pty_owner, &to_master_local,
+ &from_master_local, &pipe_sw_mutex,
+ NULL
+ };
+
+ for (HANDLE **h = handles; *h; h++)
+ **h = NULL;
+
+ _tc = cygwin_shared->tty[get_minor ()];
+
+ tcinit (false);
+
+ cygwin_shared->tty.attach (get_minor ());
+
+ /* Create synchronisation events */
+ char buf[MAX_PATH];
+
+ const char *errmsg = NULL;
+
+ if (!(output_mutex = get_ttyp ()->open_output_mutex (MAXIMUM_ALLOWED)))
+ {
+ errmsg = "open output mutex failed, %E";
+ goto err;
+ }
+ if (!(input_mutex = get_ttyp ()->open_input_mutex (MAXIMUM_ALLOWED)))
+ {
+ errmsg = "open input mutex failed, %E";
+ goto err;
+ }
+ if (!(pipe_sw_mutex
+ = get_ttyp ()->open_mutex (PIPE_SW_MUTEX, MAXIMUM_ALLOWED)))
+ {
+ errmsg = "open pipe switch mutex failed, %E";
+ goto err;
+ }
+ shared_name (buf, INPUT_AVAILABLE_EVENT, get_minor ());
+ if (!(input_available_event = OpenEvent (MAXIMUM_ALLOWED, TRUE, buf)))
+ {
+ errmsg = "open input event failed, %E";
+ goto err;
+ }
+
+ /* FIXME: Needs a method to eliminate tty races */
+ {
+ /* Create security attribute. Default permissions are 0620. */
+ security_descriptor sd;
+ sd.malloc (sizeof (SECURITY_DESCRIPTOR));
+ RtlCreateSecurityDescriptor (sd, SECURITY_DESCRIPTOR_REVISION);
+ SECURITY_ATTRIBUTES sa = { sizeof (SECURITY_ATTRIBUTES), NULL, TRUE };
+ if (!create_object_sd_from_attribute (myself->uid, myself->gid,
+ S_IFCHR | S_IRUSR | S_IWUSR | S_IWGRP,
+ sd))
+ sa.lpSecurityDescriptor = (PSECURITY_DESCRIPTOR) sd;
+ acquire_output_mutex (mutex_timeout);
+ inuse = get_ttyp ()->create_inuse (&sa);
+ get_ttyp ()->was_opened = true;
+ release_output_mutex ();
+ }
+
+ if (!get_ttyp ()->from_master_nat () || !get_ttyp ()->from_master ()
+ || !get_ttyp ()->to_master_nat () || !get_ttyp ()->to_master ())
+ {
+ errmsg = "pty handles have been closed";
+ set_errno (EACCES);
+ goto err_no_errno;
+ }
+
+ /* Three case for duplicating the pipe handles:
+ - Either we're the master. In this case, just duplicate the handles.
+ - Or, we have the right to open the master process for handle duplication.
+ In this case, just duplicate the handles.
+ - Or, we have to ask the master process itself. In this case, send our
+ pid to the master process and check the reply. The reply contains
+ either the handles, or an error code which tells us why we didn't
+ get the handles. */
+ if (myself->pid == get_ttyp ()->master_pid)
+ {
+ /* This is the most common case, just calling openpty. */
+ termios_printf ("dup handles within myself.");
+ pty_owner = GetCurrentProcess ();
+ }
+ else
+ {
+ pinfo p (get_ttyp ()->master_pid);
+ if (!p)
+ termios_printf ("*** couldn't find pty master");
+ else
+ {
+ pty_owner = OpenProcess (PROCESS_DUP_HANDLE, FALSE, p->dwProcessId);
+ if (pty_owner)
+ termios_printf ("dup handles directly since I'm the owner");
+ }
+ }
+ if (pty_owner)
+ {
+ if (!DuplicateHandle (pty_owner, get_ttyp ()->from_master_nat (),
+ GetCurrentProcess (),
+ &from_master_nat_local, 0, TRUE,
+ DUPLICATE_SAME_ACCESS))
+ {
+ termios_printf ("can't duplicate input from %u/%p, %E",
+ get_ttyp ()->master_pid,
+ get_ttyp ()->from_master_nat ());
+ __seterrno ();
+ goto err_no_msg;
+ }
+ if (!DuplicateHandle (pty_owner, get_ttyp ()->from_master (),
+ GetCurrentProcess (),
+ &from_master_local, 0, TRUE,
+ DUPLICATE_SAME_ACCESS))
+ {
+ termios_printf ("can't duplicate input from %u/%p, %E",
+ get_ttyp ()->master_pid,
+ get_ttyp ()->from_master ());
+ __seterrno ();
+ goto err_no_msg;
+ }
+ if (!DuplicateHandle (pty_owner, get_ttyp ()->to_master_nat (),
+ GetCurrentProcess (),
+ &to_master_nat_local, 0, TRUE,
+ DUPLICATE_SAME_ACCESS))
+ {
+ errmsg = "can't duplicate output, %E";
+ goto err;
+ }
+ if (!DuplicateHandle (pty_owner, get_ttyp ()->to_master (),
+ GetCurrentProcess (),
+ &to_master_local, 0, TRUE,
+ DUPLICATE_SAME_ACCESS))
+ {
+ errmsg = "can't duplicate output for cygwin, %E";
+ goto err;
+ }
+ if (pty_owner != GetCurrentProcess ())
+ CloseHandle (pty_owner);
+ }
+ else
+ {
+ pipe_request req = { GetCurrentProcessId () };
+ pipe_reply repl;
+ DWORD len;
+
+ __small_sprintf (buf, "\\\\.\\pipe\\cygwin-%S-pty%d-master-ctl",
+ &cygheap->installation_key, get_minor ());
+ termios_printf ("dup handles via master control pipe %s", buf);
+ if (!CallNamedPipe (buf, &req, sizeof req, &repl, sizeof repl,
+ &len, 500))
+ {
+ errmsg = "can't call master, %E";
+ goto err;
+ }
+ from_master_nat_local = repl.from_master_nat;
+ from_master_local = repl.from_master;
+ to_master_nat_local = repl.to_master_nat;
+ to_master_local = repl.to_master;
+ if (!from_master_nat_local || !from_master_local
+ || !to_master_nat_local || !to_master_local)
+ {
+ SetLastError (repl.error);
+ errmsg = "error duplicating pipes, %E";
+ goto err;
+ }
+ }
+ VerifyHandle (from_master_nat_local);
+ VerifyHandle (from_master_local);
+ VerifyHandle (to_master_nat_local);
+ VerifyHandle (to_master_local);
+
+ termios_printf ("duplicated from_master_nat %p->%p from pty_owner",
+ get_ttyp ()->from_master_nat (), from_master_nat_local);
+ termios_printf ("duplicated from_master %p->%p from pty_owner",
+ get_ttyp ()->from_master (), from_master_local);
+ termios_printf ("duplicated to_master_nat %p->%p from pty_owner",
+ get_ttyp ()->to_master_nat (), to_master_nat_local);
+ termios_printf ("duplicated to_master %p->%p from pty_owner",
+ get_ttyp ()->to_master (), to_master_local);
+
+ set_handle_nat (from_master_nat_local);
+ set_handle (from_master_local);
+ set_output_handle_nat (to_master_nat_local);
+ set_output_handle (to_master_local);
+
+ if (_major (myself->ctty) == DEV_CONS_MAJOR
+ && !(!pinfo (myself->ppid) && getenv ("ConEmuPID")))
+ /* This process is supposed to be a master process which is
+ running on console. Invisible console will be created in
+ primary slave process to prevent overriding code page
+ of root console by setup_locale(). */
+ /* ... except for ConEmu cygwin-connector in which this
+ code does not work as expected because it calls Win32
+ API directly rather than cygwin read()/write(). Due to
+ this behaviour, protection based on attach_mutex does
+ not take effect. */
+ get_ttyp ()->need_invisible_console = true;
+ else if (_major (myself->ctty) != DEV_CONS_MAJOR
+ && (!get_ttyp ()->invisible_console_pid
+ || !pinfo (get_ttyp ()->invisible_console_pid)))
+ /* Create a new invisible console for each pty to isolate
+ CTRL_C_EVENTs between ptys. */
+ get_ttyp ()->need_invisible_console = true;
+ else
+ {
+ acquire_attach_mutex (mutex_timeout);
+ fhandler_console::need_invisible ();
+ release_attach_mutex ();
+ }
+
+ set_open_status ();
+ return 1;
+
+err:
+ if (GetLastError () == ERROR_FILE_NOT_FOUND)
+ set_errno (ENXIO);
+ else
+ __seterrno ();
+err_no_errno:
+ termios_printf (errmsg);
+err_no_msg:
+ for (HANDLE **h = handles; *h; h++)
+ if (**h && **h != INVALID_HANDLE_VALUE)
+ CloseHandle (**h);
+ return 0;
+}
+
+bool
+fhandler_pty_slave::open_setup (int flags)
+{
+ set_flags ((flags & ~O_TEXT) | O_BINARY);
+ myself->set_ctty (this, flags);
+ report_tty_counts (this, "opened", "");
+ return fhandler_base::open_setup (flags);
+}
+
+void
+fhandler_pty_slave::cleanup ()
+{
+ /* This used to always call fhandler_pty_common::close when we were execing
+ but that caused multiple closes of the handles associated with this pty.
+ Since close_all_files is not called until after the cygwin process has
+ synced or before a non-cygwin process has exited, it should be safe to
+ just close this normally. cgf 2006-05-20 */
+ report_tty_counts (this, "closed", "");
+ fhandler_base::cleanup ();
+}
+
+int
+fhandler_pty_slave::close ()
+{
+ termios_printf ("closing last open %s handle", ttyname ());
+ if (inuse && !CloseHandle (inuse))
+ termios_printf ("CloseHandle (inuse), %E");
+ if (!ForceCloseHandle (input_available_event))
+ termios_printf ("CloseHandle (input_available_event<%p>), %E", input_available_event);
+ if (!ForceCloseHandle (get_output_handle_nat ()))
+ termios_printf ("CloseHandle (get_output_handle_nat ()<%p>), %E",
+ get_output_handle_nat ());
+ if (!ForceCloseHandle (get_handle_nat ()))
+ termios_printf ("CloseHandle (get_handle_nat ()<%p>), %E",
+ get_handle_nat ());
+ fhandler_pty_common::close ();
+ if (!ForceCloseHandle (output_mutex))
+ termios_printf ("CloseHandle (output_mutex<%p>), %E", output_mutex);
+ if (get_ttyp ()->invisible_console_pid
+ && !pinfo (get_ttyp ()->invisible_console_pid))
+ get_ttyp ()->invisible_console_pid = 0;
+ return 0;
+}
+
+int
+fhandler_pty_slave::init (HANDLE h, DWORD a, mode_t)
+{
+ int flags = 0;
+
+ a &= GENERIC_READ | GENERIC_WRITE;
+ if (a == GENERIC_READ)
+ flags = O_RDONLY;
+ if (a == GENERIC_WRITE)
+ flags = O_WRONLY;
+ if (a == (GENERIC_READ | GENERIC_WRITE))
+ flags = O_RDWR;
+
+ int ret = open_with_arch (flags);
+
+ if (ret && !cygwin_finished_initializing && !being_debugged ())
+ {
+ /* This only occurs when called from dtable::init_std_file_from_handle
+ We have been started from a non-Cygwin process. So we should become
+ pty process group leader.
+ TODO: Investigate how SIGTTIN should be handled with pure-windows
+ programs. */
+ pinfo p (tc ()->getpgid ());
+ /* We should only grab this when the process group owner for this
+ pty is a non-cygwin process or we've been started directly
+ from a non-Cygwin process with no Cygwin ancestry. */
+ if (!p || ISSTATE (p, PID_NOTCYGWIN))
+ {
+ termios_printf ("Setting process group leader to %d since %W(%d) is not a cygwin process",
+ myself->pgid, p->progname, p->pid);
+ tc ()->setpgid (myself->pgid);
+ }
+ }
+
+ if (h != INVALID_HANDLE_VALUE)
+ CloseHandle (h); /* Reopened by open */
+
+ return ret;
+}
+
+void
+fhandler_pty_slave::set_switch_to_nat_pipe (void)
+{
+ if (!isHybrid)
+ {
+ isHybrid = true;
+ setup_locale ();
+ myself->exec_dwProcessId = myself->dwProcessId; /* Set this as a marker
+ for tty::nat_fg()
+ and process_sigs() */
+ bool stdin_is_ptys = GetStdHandle (STD_INPUT_HANDLE) == get_handle ();
+ setup_for_non_cygwin_app (false, NULL, stdin_is_ptys);
+ }
+}
+
+inline static bool
+process_alive (DWORD pid)
+{
+ /* This function is very similar to _pinfo::alive(), however, this
+ can be used for non-cygwin process which is started from non-cygwin
+ shell. In addition, this checks exit code as well. */
+ if (pid == 0)
+ return false;
+ HANDLE h = OpenProcess (PROCESS_QUERY_LIMITED_INFORMATION, FALSE, pid);
+ if (h == NULL)
+ return false;
+ DWORD exit_code;
+ BOOL r = GetExitCodeProcess (h, &exit_code);
+ CloseHandle (h);
+ if (r && exit_code == STILL_ACTIVE)
+ return true;
+ return false;
+}
+
+inline static bool
+nat_pipe_owner_self (DWORD pid)
+{
+ return (pid == (myself->exec_dwProcessId ?: myself->dwProcessId));
+}
+
+/* This function is called from some pty slave functions. The purpose
+ of this function is cleaning up the nat pipe state which is marked
+ as active but actually not used anymore. This is needed only when
+ non-cygwin process is not terminated cleanly. */
+void
+fhandler_pty_slave::reset_switch_to_nat_pipe (void)
+{
+ if (h_gdb_inferior)
+ {
+ if (WaitForSingleObject (h_gdb_inferior, 0) == WAIT_TIMEOUT)
+ {
+ if (isHybrid)
+ get_ttyp ()->wait_fwd ();
+ }
+ else
+ {
+ CloseHandle (h_gdb_inferior);
+ h_gdb_inferior = NULL;
+ mutex_timeout = INFINITE;
+ if (isHybrid)
+ {
+ if (get_ttyp ()->getpgid () == myself->pgid
+ && GetStdHandle (STD_INPUT_HANDLE) == get_handle ()
+ && get_ttyp ()->pty_input_state_eq (tty::to_nat))
+ {
+ WaitForSingleObject (input_mutex, mutex_timeout);
+ acquire_attach_mutex (mutex_timeout);
+ transfer_input (tty::to_cyg, get_handle_nat (), get_ttyp (),
+ input_available_event);
+ release_attach_mutex ();
+ ReleaseMutex (input_mutex);
+ }
+ if (get_ttyp ()->master_is_running_as_service
+ && get_ttyp ()->pcon_activated)
+ /* If the master is running as service, re-attaching to
+ the console of the parent process will fail.
+ Therefore, never close pseudo console here. */
+ return;
+ bool need_restore_handles = get_ttyp ()->pcon_activated;
+ WaitForSingleObject (pipe_sw_mutex, INFINITE);
+ if (get_ttyp ()->pcon_activated)
+ close_pseudoconsole (get_ttyp ());
+ else
+ hand_over_only (get_ttyp ());
+ ReleaseMutex (pipe_sw_mutex);
+ if (need_restore_handles)
+ {
+ pinfo p (get_ttyp ()->master_pid);
+ HANDLE pty_owner =
+ OpenProcess (PROCESS_DUP_HANDLE, FALSE, p->dwProcessId);
+ if (pty_owner)
+ {
+ CloseHandle (get_handle_nat ());
+ DuplicateHandle (pty_owner,
+ get_ttyp ()->from_master_nat (),
+ GetCurrentProcess (), &get_handle_nat (),
+ 0, TRUE, DUPLICATE_SAME_ACCESS);
+ CloseHandle (get_output_handle_nat ());
+ DuplicateHandle (pty_owner,
+ get_ttyp ()->to_master_nat (),
+ GetCurrentProcess (),
+ &get_output_handle_nat (),
+ 0, TRUE, DUPLICATE_SAME_ACCESS);
+ CloseHandle (pty_owner);
+ }
+ else
+ {
+ char pipe[MAX_PATH];
+ __small_sprintf (pipe,
+ "\\\\.\\pipe\\cygwin-%S-pty%d-master-ctl",
+ &cygheap->installation_key, get_minor ());
+ pipe_request req = { GetCurrentProcessId () };
+ pipe_reply repl;
+ DWORD len;
+ if (!CallNamedPipe (pipe, &req, sizeof req,
+ &repl, sizeof repl, &len, 500))
+ return; /* What can we do? */
+ CloseHandle (get_handle_nat ());
+ set_handle_nat (repl.from_master_nat);
+ CloseHandle (get_output_handle_nat ());
+ set_output_handle_nat (repl.to_master_nat);
+ }
+ }
+ myself->exec_dwProcessId = 0;
+ isHybrid = false;
+ }
+ }
+ }
+ if (isHybrid)
+ return;
+ if (get_ttyp ()->pcon_start) /* Pseudo console initialization is on going */
+ return;
+ DWORD wait_ret = WaitForSingleObject (pipe_sw_mutex, mutex_timeout);
+ if (wait_ret == WAIT_TIMEOUT)
+ return;
+ if (!nat_pipe_owner_self (get_ttyp ()->nat_pipe_owner_pid)
+ && process_alive (get_ttyp ()->nat_pipe_owner_pid))
+ {
+ /* There is a process which owns nat pipe. */
+ ReleaseMutex (pipe_sw_mutex);
+ return;
+ }
+ /* Clean up nat pipe state */
+ get_ttyp ()->pty_input_state = tty::to_cyg;
+ get_ttyp ()->nat_pipe_owner_pid = 0;
+ get_ttyp ()->switch_to_nat_pipe = false;
+ get_ttyp ()->pcon_activated = false;
+ ReleaseMutex (pipe_sw_mutex);
+}
+
+ssize_t
+fhandler_pty_slave::write (const void *ptr, size_t len)
+{
+ ssize_t towrite = len;
+
+ bg_check_types bg = bg_check (SIGTTOU);
+ if (bg <= bg_eof)
+ return (ssize_t) bg;
+
+ termios_printf ("pty%d, write(%p, %lu)", get_minor (), ptr, len);
+
+ push_process_state process_state (PID_TTYOU);
+
+ acquire_output_mutex (mutex_timeout);
+ if (!process_opost_output (get_output_handle (), ptr, towrite, false,
+ get_ttyp (), is_nonblocking ()))
+ {
+ DWORD err = GetLastError ();
+ termios_printf ("WriteFile failed, %E");
+ switch (err)
+ {
+ case ERROR_NO_DATA:
+ err = ERROR_IO_DEVICE;
+ fallthrough;
+ default:
+ __seterrno_from_win_error (err);
+ }
+ towrite = -1;
+ }
+ release_output_mutex ();
+
+ return towrite;
+}
+
+/* This function is called from some slave pty reading functions
+ to switch active pipe to cygwin pipe (not nat pipe) temporarily
+ even though there is another process which owns nat pipe. This
+ is needed if cygwin process reads input while another non-cygwin
+ process is running at the same time.
+ (e.g. Something like "cat | non-cygwin-app") */
+void
+fhandler_pty_slave::mask_switch_to_nat_pipe (bool mask, bool xfer)
+{
+ char name[MAX_PATH];
+ shared_name (name, TTY_SLAVE_READING, get_minor ());
+ HANDLE masked = OpenEvent (READ_CONTROL, FALSE, name);
+ CloseHandle (masked);
+
+ WaitForSingleObject (input_mutex, mutex_timeout);
+ if (mask)
+ {
+ if (InterlockedIncrement (&num_reader) == 1)
+ slave_reading = CreateEvent (&sec_none_nih, TRUE, FALSE, name);
+ }
+ else if (InterlockedDecrement (&num_reader) == 0)
+ CloseHandle (slave_reading);
+
+ /* This is needed when cygwin-app is started from non-cygwin app if
+ pseudo console is disabled. */
+ bool need_xfer = get_ttyp ()->nat_fg (get_ttyp ()->getpgid ())
+ && get_ttyp ()->switch_to_nat_pipe && !get_ttyp ()->pcon_activated;
+
+ /* In GDB, transfer input based on setpgid() does not work because
+ GDB may not set terminal process group properly. Therefore,
+ transfer input here if isHybrid is set. */
+ bool need_gdb_xfer =
+ isHybrid && GetStdHandle (STD_INPUT_HANDLE) == get_handle ();
+ if (!!masked != mask && xfer && (need_gdb_xfer || need_xfer))
+ {
+ if (mask && get_ttyp ()->pty_input_state_eq (tty::to_nat))
+ {
+ acquire_attach_mutex (mutex_timeout);
+ transfer_input (tty::to_cyg, get_handle_nat (), get_ttyp (),
+ input_available_event);
+ release_attach_mutex ();
+ }
+ else if (!mask && get_ttyp ()->pty_input_state_eq (tty::to_cyg))
+ {
+ acquire_attach_mutex (mutex_timeout);
+ transfer_input (tty::to_nat, get_handle (), get_ttyp (),
+ input_available_event);
+ release_attach_mutex ();
+ }
+ }
+ ReleaseMutex (input_mutex);
+}
+
+/* Return true when the non-cygwin app is reading the input. */
+bool
+fhandler_pty_common::to_be_read_from_nat_pipe (void)
+{
+ if (!get_ttyp ()->switch_to_nat_pipe)
+ return false;
+
+ char name[MAX_PATH];
+ shared_name (name, TTY_SLAVE_READING, get_minor ());
+ HANDLE masked = OpenEvent (READ_CONTROL, FALSE, name);
+ CloseHandle (masked);
+
+ if (masked) /* The foreground process is cygwin process */
+ return false;
+
+ if (!pinfo (get_ttyp ()->getpgid ()))
+ /* GDB may set invalid process group for non-cygwin process. */
+ return true;
+
+ return get_ttyp ()->nat_fg (get_ttyp ()->getpgid ());
+}
+
+void
+fhandler_pty_slave::read (void *ptr, size_t& len)
+{
+ ssize_t totalread = 0;
+ int vmin = 0;
+ int vtime = 0; /* Initialized to prevent -Wuninitialized warning */
+ size_t readlen;
+ DWORD bytes_in_pipe;
+ char buf[INP_BUFFER_SIZE];
+ DWORD time_to_wait;
+ char *ptr0 = (char *) ptr;
+
+ bg_check_types bg = bg_check (SIGTTIN);
+ if (bg <= bg_eof)
+ {
+ len = (size_t) bg;
+ return;
+ }
+
+ termios_printf ("read(%p, %lu) handle %p", ptr, len, get_handle ());
+
+ push_process_state process_state (PID_TTYIN);
+
+ if (ptr) /* Indicating not tcflush(). */
+ mask_switch_to_nat_pipe (true, true);
+
+ if (is_nonblocking () || !ptr) /* Indicating tcflush(). */
+ time_to_wait = 0;
+ else if ((get_ttyp ()->ti.c_lflag & ICANON))
+ time_to_wait = INFINITE;
+ else
+ {
+ vmin = get_ttyp ()->ti.c_cc[VMIN];
+ if (vmin > INP_BUFFER_SIZE)
+ vmin = INP_BUFFER_SIZE;
+ vtime = get_ttyp ()->ti.c_cc[VTIME];
+ if (vmin < 0)
+ vmin = 0;
+ if (vtime < 0)
+ vtime = 0;
+ if (!vmin && !vtime)
+ time_to_wait = 0;
+ else
+ time_to_wait = !vtime ? INFINITE : 100 * vtime;
+ }
+
+wait_retry:
+ while (len)
+ {
+ switch (cygwait (input_available_event, time_to_wait))
+ {
+ case WAIT_OBJECT_0:
+ break;
+ case WAIT_SIGNALED:
+ if (totalread > 0)
+ goto out;
+ termios_printf ("wait catched signal");
+ set_sig_errno (EINTR);
+ totalread = -1;
+ goto out;
+ case WAIT_CANCELED:
+ process_state.pop ();
+ pthread::static_cancel_self ();
+ /*NOTREACHED*/
+ case WAIT_TIMEOUT:
+ termios_printf ("wait timed out, time_to_wait %u", time_to_wait);
+ /* No error condition when called from tcflush. */
+ if (!totalread && ptr)
+ {
+ set_sig_errno (EAGAIN);
+ totalread = -1;
+ }
+ goto out;
+ default:
+ termios_printf ("wait for input event failed, %E");
+ if (!totalread)
+ {
+ __seterrno ();
+ totalread = -1;
+ }
+ goto out;
+ }
+ /* Now that we know that input is available we have to grab the
+ input mutex. */
+ switch (cygwait (input_mutex, 1000))
+ {
+ case WAIT_OBJECT_0:
+ case WAIT_ABANDONED_0:
+ break;
+ case WAIT_SIGNALED:
+ if (totalread > 0)
+ goto out;
+ termios_printf ("wait for mutex caught signal");
+ set_sig_errno (EINTR);
+ totalread = -1;
+ goto out;
+ case WAIT_CANCELED:
+ process_state.pop ();
+ pthread::static_cancel_self ();
+ /*NOTREACHED*/
+ case WAIT_TIMEOUT:
+ termios_printf ("failed to acquire input mutex after input event "
+ "arrived");
+ /* If we have a timeout, we can simply handle this failure to
+ grab the mutex as an EAGAIN situation. Otherwise, if this
+ is an infinitely blocking read, restart the loop. */
+ if (time_to_wait != INFINITE)
+ {
+ if (!totalread)
+ {
+ set_sig_errno (EAGAIN);
+ totalread = -1;
+ }
+ goto out;
+ }
+ continue;
+ default:
+ termios_printf ("wait for input mutex failed, %E");
+ if (!totalread)
+ {
+ __seterrno ();
+ totalread = -1;
+ }
+ goto out;
+ }
+ if (!IsEventSignalled (input_available_event))
+ { /* Maybe another thread has processed input. */
+ ReleaseMutex (input_mutex);
+ goto wait_retry;
+ }
+
+ if (!bytes_available (bytes_in_pipe))
+ {
+ ReleaseMutex (input_mutex);
+ set_errno (EIO);
+ totalread = -1;
+ goto out;
+ }
+
+ if (ptr && !bytes_in_pipe && !vmin && !time_to_wait)
+ {
+ ReleaseMutex (input_mutex);
+ mask_switch_to_nat_pipe (false, false);
+ len = (size_t) bytes_in_pipe;
+ return;
+ }
+
+ readlen = bytes_in_pipe ? MIN (len, sizeof (buf)) : 0;
+ if (get_ttyp ()->ti.c_lflag & ICANON && ptr)
+ readlen = MIN (bytes_in_pipe, readlen);
+
+#if 0
+ /* Why on earth is the read length reduced to vmin, even if more bytes
+ are available *and* len is bigger *and* the local buf is big enough?
+ Disable this code for now, it looks like a remnant of old. */
+ if (ptr && vmin && readlen > (unsigned) vmin)
+ readlen = vmin;
+#endif
+
+ DWORD n = 0;
+ if (readlen)
+ {
+ termios_printf ("reading %lu bytes (vtime %d)", readlen, vtime);
+ if (!ReadFile (get_handle (), buf, readlen, &n, NULL))
+ {
+ termios_printf ("read failed, %E");
+ ReleaseMutex (input_mutex);
+ set_errno (EIO);
+ totalread = -1;
+ goto out;
+ }
+ else
+ {
+ /* MSDN states that 5th prameter can be used to determine total
+ number of bytes in pipe, but for some reason this number doesn't
+ change after successful read. So we have to peek into the pipe
+ again to see if input is still available */
+ if (!bytes_available (bytes_in_pipe))
+ {
+ ReleaseMutex (input_mutex);
+ set_errno (EIO);
+ totalread = -1;
+ goto out;
+ }
+ if (n)
+ {
+ if (!(!ptr && len == UINT_MAX)) /* not tcflush() */
+ len -= n;
+ totalread += n;
+ if (ptr)
+ {
+ memcpy (ptr, buf, n);
+ ptr = (char *) ptr + n;
+ }
+ }
+ }
+ }
+
+ if (!bytes_in_pipe)
+ ResetEvent (input_available_event);
+
+ ReleaseMutex (input_mutex);
+
+ if (!ptr)
+ {
+ if (!bytes_in_pipe)
+ break;
+ continue;
+ }
+
+ if (get_ttyp ()->read_retval < 0) // read error
+ {
+ set_errno (-get_ttyp ()->read_retval);
+ totalread = -1;
+ break;
+ }
+ if (get_ttyp ()->read_retval == 0) //EOF
+ {
+ termios_printf ("saw EOF");
+ break;
+ }
+ if (get_ttyp ()->ti.c_lflag & ICANON || is_nonblocking ())
+ break;
+ if (vmin && totalread >= vmin)
+ break;
+
+ /* vmin == 0 && vtime == 0:
+ * we've already read all input, if any, so return immediately
+ * vmin == 0 && vtime > 0:
+ * we've waited for input 10*vtime ms in WFSO(input_available_event),
+ * no matter whether any input arrived, we shouldn't wait any longer,
+ * so return immediately
+ * vmin > 0 && vtime == 0:
+ * here, totalread < vmin, so continue waiting until more data
+ * arrive
+ * vmin > 0 && vtime > 0:
+ * similar to the previous here, totalread < vmin, and timer
+ * hadn't expired -- WFSO(input_available_event) != WAIT_TIMEOUT,
+ * so "restart timer" and wait until more data arrive
+ */
+
+ if (vmin == 0)
+ break;
+ }
+out:
+ termios_printf ("%d = read(%p, %lu)", totalread, ptr, len);
+ len = (size_t) totalread;
+ if (ptr0)
+ { /* Not tcflush() */
+ bool saw_eol = totalread > 0 && strchr ("\r\n", ptr0[totalread -1]);
+ mask_switch_to_nat_pipe (false, saw_eol || len == 0);
+ }
+}
+
+int
+fhandler_pty_slave::dup (fhandler_base *child, int flags)
+{
+ /* This code was added in Oct 2001 for some undisclosed reason.
+ However, setting the controlling tty on a dup causes rxvt to
+ hang when the parent does a dup since the controlling pgid changes.
+ Specifically testing for -2 (ctty has been setsid'ed) works around
+ this problem. However, it's difficult to see scenarios in which you
+ have a dup'able fd, no controlling tty, and not having run setsid.
+ So, we might want to consider getting rid of the set_ctty in tty-like dup
+ methods entirely at some point */
+ if (myself->ctty != -2)
+ myself->set_ctty (this, flags);
+ report_tty_counts (child, "duped slave", "");
+ return 0;
+}
+
+int
+fhandler_pty_master::dup (fhandler_base *child, int)
+{
+ report_tty_counts (child, "duped master", "");
+ return 0;
+}
+
+int
+fhandler_pty_slave::tcgetattr (struct termios *t)
+{
+ *t = get_ttyp ()->ti;
+
+ /* Workaround for rlwrap */
+ cygheap_fdenum cfd (false);
+ while (cfd.next () >= 0)
+ if (cfd->get_major () == DEV_PTYM_MAJOR
+ && cfd->get_minor () == get_minor ())
+ {
+ if (get_ttyp ()->pcon_start)
+ t->c_lflag &= ~(ICANON | ECHO);
+ if (get_ttyp ()->pcon_activated)
+ t->c_iflag &= ~ICRNL;
+ break;
+ }
+ return 0;
+}
+
+int
+fhandler_pty_slave::tcsetattr (int, const struct termios *t)
+{
+ acquire_output_mutex (mutex_timeout);
+ get_ttyp ()->ti = *t;
+ release_output_mutex ();
+ return 0;
+}
+
+int
+fhandler_pty_slave::tcflush (int queue)
+{
+ int ret = 0;
+
+ termios_printf ("tcflush(%d) handle %p", queue, get_handle ());
+
+ if (queue == TCIFLUSH || queue == TCIOFLUSH)
+ {
+ size_t len = UINT_MAX;
+ read (NULL, len);
+ ret = ((int) len) >= 0 ? 0 : -1;
+ }
+ if (queue == TCOFLUSH || queue == TCIOFLUSH)
+ {
+ /* do nothing for now. */
+ }
+
+ termios_printf ("%d=tcflush(%d)", ret, queue);
+ return ret;
+}
+
+int
+fhandler_pty_slave::ioctl (unsigned int cmd, void *arg)
+{
+ termios_printf ("ioctl (%x)", cmd);
+ int res = fhandler_termios::ioctl (cmd, arg);
+ if (res <= 0)
+ return res;
+
+ if (myself->pgid && get_ttyp ()->getpgid () != myself->pgid
+ && (unsigned) myself->ctty == FHDEV (DEV_PTYS_MAJOR, get_minor ())
+ && (get_ttyp ()->ti.c_lflag & TOSTOP))
+ {
+ /* background process */
+ termios_printf ("bg ioctl pgid %d, tpgid %d, %s", myself->pgid,
+ get_ttyp ()->getpgid (), myctty ());
+ raise (SIGTTOU);
+ }
+
+ int retval;
+ switch (cmd)
+ {
+ case TIOCGWINSZ:
+ case TIOCSWINSZ:
+ break;
+ case TIOCGPGRP:
+ {
+ pid_t pid = this->tcgetpgrp ();
+ if (pid < 0)
+ retval = -1;
+ else
+ {
+ *((pid_t *) arg) = pid;
+ retval = 0;
+ }
+ }
+ goto out;
+ case TIOCSPGRP:
+ retval = this->tcsetpgrp ((pid_t) (intptr_t) arg);
+ goto out;
+ case FIONREAD:
+ {
+ DWORD n;
+ if (!bytes_available (n))
+ {
+ set_errno (EINVAL);
+ retval = -1;
+ }
+ else
+ {
+ *(int *) arg = (int) n;
+ retval = 0;
+ }
+ }
+ goto out;
+ default:
+ return fhandler_base::ioctl (cmd, arg);
+ }
+
+ acquire_output_mutex (mutex_timeout);
+
+ get_ttyp ()->cmd = cmd;
+ get_ttyp ()->ioctl_retval = 0;
+ switch (cmd)
+ {
+ case TIOCGWINSZ:
+ get_ttyp ()->arg.winsize = get_ttyp ()->winsize;
+ *(struct winsize *) arg = get_ttyp ()->arg.winsize;
+ get_ttyp ()->winsize = get_ttyp ()->arg.winsize;
+ break;
+ case TIOCSWINSZ:
+ if (get_ttyp ()->winsize.ws_row != ((struct winsize *) arg)->ws_row
+ || get_ttyp ()->winsize.ws_col != ((struct winsize *) arg)->ws_col
+ || get_ttyp ()->winsize.ws_ypixel != ((struct winsize *) arg)->ws_ypixel
+ || get_ttyp ()->winsize.ws_xpixel != ((struct winsize *) arg)->ws_xpixel
+ )
+ {
+ if (get_ttyp ()->pcon_activated && get_ttyp ()->nat_pipe_owner_pid)
+ resize_pseudo_console ((struct winsize *) arg);
+ get_ttyp ()->arg.winsize = *(struct winsize *) arg;
+ get_ttyp ()->winsize = *(struct winsize *) arg;
+ get_ttyp ()->kill_pgrp (SIGWINCH);
+ }
+ break;
+ }
+
+ release_output_mutex ();
+ retval = get_ttyp ()->ioctl_retval;
+ if (retval < 0)
+ {
+ set_errno (-retval);
+ retval = -1;
+ }
+
+out:
+ termios_printf ("%d = ioctl(%x)", retval, cmd);
+ return retval;
+}
+
+int
+fhandler_pty_slave::fstat (struct stat *st)
+{
+ fhandler_base::fstat (st);
+
+ bool to_close = false;
+ if (!input_available_event)
+ {
+ char buf[MAX_PATH];
+ shared_name (buf, INPUT_AVAILABLE_EVENT, get_minor ());
+ input_available_event = OpenEvent (READ_CONTROL, TRUE, buf);
+ if (input_available_event)
+ to_close = true;
+ }
+ st->st_mode = S_IFCHR;
+ if (!input_available_event
+ || get_object_attribute (input_available_event, &st->st_uid, &st->st_gid,
+ &st->st_mode))
+ {
+ /* If we can't access the ACL, or if the tty doesn't actually exist,
+ then fake uid and gid to strict, system-like values. */
+ st->st_mode = S_IFCHR | S_IRUSR | S_IWUSR;
+ st->st_uid = 18;
+ st->st_gid = 544;
+ }
+ if (to_close)
+ CloseHandle (input_available_event);
+ return 0;
+}
+
+int
+fhandler_pty_slave::facl (int cmd, int nentries, aclent_t *aclbufp)
+{
+ int res = -1;
+ bool to_close = false;
+ security_descriptor sd;
+ mode_t attr = S_IFCHR;
+
+ switch (cmd)
+ {
+ case SETACL:
+ if (!aclsort (nentries, 0, aclbufp))
+ set_errno (ENOTSUP);
+ break;
+ case GETACL:
+ if (!aclbufp)
+ {
+ set_errno (EFAULT);
+ break;
+ }
+ fallthrough;
+ case GETACLCNT:
+ if (!input_available_event)
+ {
+ char buf[MAX_PATH];
+ shared_name (buf, INPUT_AVAILABLE_EVENT, get_minor ());
+ input_available_event = OpenEvent (READ_CONTROL, TRUE, buf);
+ if (input_available_event)
+ to_close = true;
+ }
+ if (!input_available_event
+ || get_object_sd (input_available_event, sd))
+ {
+ res = get_posix_access (NULL, &attr, NULL, NULL, aclbufp, nentries);
+ if (aclbufp && res == MIN_ACL_ENTRIES)
+ {
+ aclbufp[0].a_perm = S_IROTH | S_IWOTH;
+ aclbufp[0].a_id = 18;
+ aclbufp[1].a_id = 544;
+ }
+ break;
+ }
+ if (cmd == GETACL)
+ res = get_posix_access (sd, &attr, NULL, NULL, aclbufp, nentries);
+ else
+ res = get_posix_access (sd, &attr, NULL, NULL, NULL, 0);
+ break;
+ default:
+ set_errno (EINVAL);
+ break;
+ }
+ if (to_close)
+ CloseHandle (input_available_event);
+ return res;
+}
+
+/* Helper function for fchmod and fchown, which just opens all handles
+ and signals success via bool return. */
+bool
+fhandler_pty_slave::fch_open_handles (bool chown)
+{
+ char buf[MAX_PATH];
+ DWORD write_access = WRITE_DAC | (chown ? WRITE_OWNER : 0);
+
+ _tc = cygwin_shared->tty[get_minor ()];
+ shared_name (buf, INPUT_AVAILABLE_EVENT, get_minor ());
+ input_available_event = OpenEvent (READ_CONTROL | write_access,
+ TRUE, buf);
+ output_mutex = get_ttyp ()->open_output_mutex (write_access);
+ input_mutex = get_ttyp ()->open_input_mutex (write_access);
+ pipe_sw_mutex = get_ttyp ()->open_mutex (PIPE_SW_MUTEX, write_access);
+ inuse = get_ttyp ()->open_inuse (write_access);
+ if (!input_available_event || !output_mutex || !input_mutex || !inuse)
+ {
+ __seterrno ();
+ return false;
+ }
+ return true;
+}
+
+/* Helper function for fchmod and fchown, which sets the new security
+ descriptor on all objects representing the pty. */
+int
+fhandler_pty_slave::fch_set_sd (security_descriptor &sd, bool chown)
+{
+ security_descriptor sd_old;
+
+ get_object_sd (input_available_event, sd_old);
+ if (!set_object_sd (input_available_event, sd, chown)
+ && !set_object_sd (output_mutex, sd, chown)
+ && !set_object_sd (input_mutex, sd, chown)
+ && !set_object_sd (inuse, sd, chown))
+ return 0;
+ set_object_sd (input_available_event, sd_old, chown);
+ set_object_sd (output_mutex, sd_old, chown);
+ set_object_sd (input_mutex, sd_old, chown);
+ set_object_sd (inuse, sd_old, chown);
+ return -1;
+}
+
+/* Helper function for fchmod and fchown, which closes all object handles in
+ the pty. */
+void
+fhandler_pty_slave::fch_close_handles ()
+{
+ close_maybe (input_available_event);
+ close_maybe (output_mutex);
+ close_maybe (input_mutex);
+ close_maybe (inuse);
+}
+
+int
+fhandler_pty_slave::fchmod (mode_t mode)
+{
+ int ret = -1;
+ bool to_close = false;
+ security_descriptor sd;
+ uid_t uid;
+ gid_t gid;
+ mode_t orig_mode = S_IFCHR;
+
+ if (!input_available_event)
+ {
+ to_close = true;
+ if (!fch_open_handles (false))
+ goto errout;
+ }
+ sd.malloc (sizeof (SECURITY_DESCRIPTOR));
+ RtlCreateSecurityDescriptor (sd, SECURITY_DESCRIPTOR_REVISION);
+ if (!get_object_attribute (input_available_event, &uid, &gid, &orig_mode)
+ && !create_object_sd_from_attribute (uid, gid, S_IFCHR | mode, sd))
+ ret = fch_set_sd (sd, false);
+errout:
+ if (to_close)
+ fch_close_handles ();
+ return ret;
+}
+
+int
+fhandler_pty_slave::fchown (uid_t uid, gid_t gid)
+{
+ int ret = -1;
+ bool to_close = false;
+ security_descriptor sd;
+ uid_t o_uid;
+ gid_t o_gid;
+ mode_t mode = S_IFCHR;
+
+ if (uid == ILLEGAL_UID && gid == ILLEGAL_GID)
+ return 0;
+ if (!input_available_event)
+ {
+ to_close = true;
+ if (!fch_open_handles (true))
+ goto errout;
+ }
+ sd.malloc (sizeof (SECURITY_DESCRIPTOR));
+ RtlCreateSecurityDescriptor (sd, SECURITY_DESCRIPTOR_REVISION);
+ if (!get_object_attribute (input_available_event, &o_uid, &o_gid, &mode))
+ {
+ if (uid == ILLEGAL_UID)
+ uid = o_uid;
+ if (gid == ILLEGAL_GID)
+ gid = o_gid;
+ if (uid == o_uid && gid == o_gid)
+ ret = 0;
+ else if (!create_object_sd_from_attribute (uid, gid, mode, sd))
+ ret = fch_set_sd (sd, true);
+ }
+errout:
+ if (to_close)
+ fch_close_handles ();
+ return ret;
+}
+
+/*******************************************************
+ fhandler_pty_master
+*/
+fhandler_pty_master::fhandler_pty_master (int unit)
+ : fhandler_pty_common (), pktmode (0), master_ctl (NULL),
+ master_thread (NULL), from_master_nat (NULL), to_master_nat (NULL),
+ from_slave_nat (NULL), to_slave_nat (NULL), echo_r (NULL), echo_w (NULL),
+ dwProcessId (0), to_master (NULL), from_master (NULL),
+ master_fwd_thread (NULL)
+{
+ if (unit >= 0)
+ dev ().parse (DEV_PTYM_MAJOR, unit);
+ set_name ("/dev/ptmx");
+}
+
+int
+fhandler_pty_master::open (int flags, mode_t)
+{
+ if (!setup ())
+ return 0;
+ set_open_status ();
+ dwProcessId = GetCurrentProcessId ();
+ return 1;
+}
+
+bool
+fhandler_pty_master::open_setup (int flags)
+{
+ set_flags ((flags & ~O_TEXT) | O_BINARY);
+ char buf[sizeof ("opened pty master for ptyNNNNNNNNNNN")];
+ __small_sprintf (buf, "opened pty master for pty%d", get_minor ());
+ report_tty_counts (this, buf, "");
+ return fhandler_base::open_setup (flags);
+}
+
+off_t
+fhandler_pty_common::lseek (off_t, int)
+{
+ set_errno (ESPIPE);
+ return -1;
+}
+
+int
+fhandler_pty_common::close ()
+{
+ termios_printf ("pty%d <%p,%p> closing",
+ get_minor (), get_handle (), get_output_handle ());
+ if (!ForceCloseHandle (input_mutex))
+ termios_printf ("CloseHandle (input_mutex<%p>), %E", input_mutex);
+ if (!ForceCloseHandle (pipe_sw_mutex))
+ termios_printf ("CloseHandle (pipe_sw_mutex<%p>), %E", pipe_sw_mutex);
+ if (!ForceCloseHandle1 (get_handle (), from_pty))
+ termios_printf ("CloseHandle (get_handle ()<%p>), %E", get_handle ());
+ if (!ForceCloseHandle1 (get_output_handle (), to_pty))
+ termios_printf ("CloseHandle (get_output_handle ()<%p>), %E",
+ get_output_handle ());
+
+ return 0;
+}
+
+void
+fhandler_pty_common::resize_pseudo_console (struct winsize *ws)
+{
+ COORD size;
+ size.X = ws->ws_col;
+ size.Y = ws->ws_row;
+ HPCON_INTERNAL hpcon_local;
+ HANDLE pcon_owner =
+ OpenProcess (PROCESS_DUP_HANDLE, FALSE, get_ttyp ()->nat_pipe_owner_pid);
+ DuplicateHandle (pcon_owner, get_ttyp ()->h_pcon_write_pipe,
+ GetCurrentProcess (), &hpcon_local.hWritePipe,
+ 0, FALSE, DUPLICATE_SAME_ACCESS);
+ acquire_attach_mutex (mutex_timeout);
+ ResizePseudoConsole ((HPCON) &hpcon_local, size);
+ release_attach_mutex ();
+ CloseHandle (pcon_owner);
+ CloseHandle (hpcon_local.hWritePipe);
+}
+
+void
+fhandler_pty_master::cleanup ()
+{
+ report_tty_counts (this, "closing master", "");
+ if (archetype)
+ from_master_nat = from_master =
+ to_master_nat = to_master = from_slave_nat = to_slave_nat = NULL;
+ fhandler_base::cleanup ();
+}
+
+int
+fhandler_pty_master::close ()
+{
+ OBJECT_BASIC_INFORMATION obi;
+ NTSTATUS status;
+
+ termios_printf ("closing from_master_nat(%p)/from_master(%p)/to_master_nat(%p)/to_master(%p) since we own them(%u)",
+ from_master_nat, from_master,
+ to_master_nat, to_master, dwProcessId);
+ if (cygwin_finished_initializing)
+ {
+ if (master_ctl && get_ttyp ()->master_pid == myself->pid)
+ {
+ char buf[MAX_PATH];
+ pipe_request req = { (DWORD) -1 };
+ pipe_reply repl;
+ DWORD len;
+
+ __small_sprintf (buf, "\\\\.\\pipe\\cygwin-%S-pty%d-master-ctl",
+ &cygheap->installation_key, get_minor ());
+ acquire_output_mutex (mutex_timeout);
+ if (master_ctl)
+ {
+ CallNamedPipe (buf, &req, sizeof req, &repl, sizeof repl, &len,
+ 500);
+ CloseHandle (master_ctl);
+ master_thread->detach ();
+ get_ttyp ()->set_master_ctl_closed ();
+ master_ctl = NULL;
+ }
+ release_output_mutex ();
+ get_ttyp ()->stop_fwd_thread = true;
+ WriteFile (to_master_nat, "", 0, &len, NULL);
+ master_fwd_thread->detach ();
+ if (helper_goodbye)
+ {
+ SetEvent (helper_goodbye);
+ WaitForSingleObject (helper_h_process, INFINITE);
+ CloseHandle (helper_h_process);
+ CloseHandle (helper_goodbye);
+ helper_pid = 0;
+ helper_h_process = 0;
+ helper_goodbye = NULL;
+ }
+ }
+ }
+
+ /* Check if the last master handle has been closed. If so, set
+ input_available_event to wake up potentially waiting slaves. */
+ acquire_output_mutex (mutex_timeout);
+ status = NtQueryObject (get_output_handle (), ObjectBasicInformation,
+ &obi, sizeof obi, NULL);
+ fhandler_pty_common::close ();
+ release_output_mutex ();
+ if (!ForceCloseHandle (output_mutex))
+ termios_printf ("CloseHandle (output_mutex<%p>), %E", output_mutex);
+ if (!NT_SUCCESS (status))
+ debug_printf ("NtQueryObject: %y", status);
+ else if (obi.HandleCount == 1)
+ {
+ termios_printf ("Closing last master of pty%d", get_minor ());
+ if (get_ttyp ()->getsid () > 0)
+ kill (get_ttyp ()->getsid (), SIGHUP);
+ SetEvent (input_available_event);
+ }
+
+ if (!ForceCloseHandle (from_master_nat))
+ termios_printf ("error closing from_master_nat %p, %E", from_master_nat);
+ if (!ForceCloseHandle (to_master_nat))
+ termios_printf ("error closing to_master_nat %p, %E", to_master_nat);
+ from_master_nat = to_master_nat = NULL;
+ if (!ForceCloseHandle (from_slave_nat))
+ termios_printf ("error closing from_slave_nat %p, %E", from_slave_nat);
+ from_slave_nat = NULL;
+ if (!ForceCloseHandle (to_master))
+ termios_printf ("error closing to_master %p, %E", to_master);
+ to_master = NULL;
+ ForceCloseHandle (echo_r);
+ ForceCloseHandle (echo_w);
+ echo_r = echo_w = NULL;
+ if (to_slave_nat)
+ ForceCloseHandle (to_slave_nat);
+ to_slave_nat = NULL;
+
+ if (have_execed || get_ttyp ()->master_pid != myself->pid)
+ termios_printf ("not clearing: %d, master_pid %d",
+ have_execed, get_ttyp ()->master_pid);
+ if (!ForceCloseHandle (input_available_event))
+ termios_printf ("CloseHandle (input_available_event<%p>), %E",
+ input_available_event);
+
+ /* The from_master must be closed last so that the same pty is not
+ allocated before cleaning up the other corresponding instances. */
+ if (!ForceCloseHandle (from_master))
+ termios_printf ("error closing from_master %p, %E", from_master);
+ from_master = NULL;
+
+ return 0;
+}
+
+ssize_t
+fhandler_pty_master::write (const void *ptr, size_t len)
+{
+ ssize_t ret;
+ char *p = (char *) ptr;
+ termios &ti = tc ()->ti;
+
+ bg_check_types bg = bg_check (SIGTTOU);
+ if (bg <= bg_eof)
+ return (ssize_t) bg;
+
+ push_process_state process_state (PID_TTYOU);
+
+ if (get_ttyp ()->pcon_start)
+ { /* Reaches here when pseudo console initialization is on going. */
+ /* Pseudo condole support uses "CSI6n" to get cursor position.
+ If the reply for "CSI6n" is divided into multiple writes,
+ pseudo console sometimes does not recognize it. Therefore,
+ put them together into wpbuf and write all at once. */
+ static const int wpbuf_len = strlen ("\033[32768;32868R");
+ static char wpbuf[wpbuf_len];
+ static int ixput = 0;
+ static int state = 0;
+
+ DWORD n;
+ WaitForSingleObject (input_mutex, mutex_timeout);
+ for (size_t i = 0; i < len; i++)
+ {
+ if (p[i] == '\033')
+ {
+ if (ixput)
+ line_edit (wpbuf, ixput, ti, &ret);
+ ixput = 0;
+ state = 1;
+ }
+ if (state == 1)
+ {
+ if (ixput < wpbuf_len)
+ wpbuf[ixput++] = p[i];
+ else
+ {
+ if (!get_ttyp ()->req_xfer_input)
+ WriteFile (to_slave_nat, wpbuf, ixput, &n, NULL);
+ ixput = 0;
+ wpbuf[ixput++] = p[i];
+ }
+ }
+ else
+ line_edit (p + i, 1, ti, &ret);
+ if (state == 1 && p[i] == 'R')
+ state = 2;
+ }
+ if (state == 2)
+ {
+ /* req_xfer_input is true if "ESC[6n" was sent just for
+ triggering transfer_input() in master. In this case,
+ the responce sequence should not be written. */
+ if (!get_ttyp ()->req_xfer_input)
+ WriteFile (to_slave_nat, wpbuf, ixput, &n, NULL);
+ ixput = 0;
+ state = 0;
+ get_ttyp ()->req_xfer_input = false;
+ get_ttyp ()->pcon_start = false;
+ }
+ ReleaseMutex (input_mutex);
+
+ if (!get_ttyp ()->pcon_start)
+ { /* Pseudo console initialization has been done in above code. */
+ pinfo pp (get_ttyp ()->pcon_start_pid);
+ bool pcon_fg = (pp && get_ttyp ()->getpgid () == pp->pgid);
+ /* GDB may set WINPID rather than cygwin PID to process group
+ when the debugged process is a non-cygwin process.*/
+ pcon_fg |= !pinfo (get_ttyp ()->getpgid ());
+ if (get_ttyp ()->switch_to_nat_pipe && pcon_fg
+ && get_ttyp ()->pty_input_state_eq (tty::to_cyg))
+ {
+ /* This accept_input() call is needed in order to transfer input
+ which is not accepted yet to non-cygwin pipe. */
+ if (get_readahead_valid ())
+ accept_input ();
+ WaitForSingleObject (input_mutex, mutex_timeout);
+ acquire_attach_mutex (mutex_timeout);
+ fhandler_pty_slave::transfer_input (tty::to_nat, from_master,
+ get_ttyp (),
+ input_available_event);
+ release_attach_mutex ();
+ ReleaseMutex (input_mutex);
+ }
+ get_ttyp ()->pcon_start_pid = 0;
+ }
+
+ return len;
+ }
+
+ /* Write terminal input to to_slave_nat pipe instead of output_handle
+ if current application is native console application. */
+ WaitForSingleObject (input_mutex, mutex_timeout);
+ if (to_be_read_from_nat_pipe () && get_ttyp ()->pcon_activated
+ && get_ttyp ()->pty_input_state == tty::to_nat)
+ { /* Reaches here when non-cygwin app is foreground and pseudo console
+ is activated. */
+ tmp_pathbuf tp;
+ char *buf = (char *) ptr;
+ size_t nlen = len;
+ if (get_ttyp ()->term_code_page != CP_UTF8)
+ {
+ static mbstate_t mbp;
+ buf = tp.c_get ();
+ nlen = NT_MAX_PATH;
+ convert_mb_str (CP_UTF8, buf, &nlen,
+ get_ttyp ()->term_code_page, (const char *) ptr, len,
+ &mbp);
+ }
+
+ for (size_t i = 0; i < nlen; i++)
+ {
+ process_sig_state r = process_sigs (buf[i], get_ttyp (), this);
+ if (r == done_with_debugger)
+ {
+ for (size_t j = i; j < nlen - 1; j++)
+ buf[j] = buf[j + 1];
+ nlen--;
+ i--;
+ }
+ process_stop_start (buf[i], get_ttyp ());
+ }
+
+ DWORD n;
+ WriteFile (to_slave_nat, buf, nlen, &n, NULL);
+ ReleaseMutex (input_mutex);
+
+ return len;
+ }
+
+ /* The code path reaches here when pseudo console is not activated
+ or cygwin process is foreground even though pseudo console is
+ activated. */
+
+ /* This input transfer is needed when cygwin-app which is started from
+ non-cygwin app is terminated if pseudo console is disabled. */
+ if (to_be_read_from_nat_pipe () && !get_ttyp ()->pcon_activated
+ && get_ttyp ()->pty_input_state == tty::to_cyg)
+ {
+ acquire_attach_mutex (mutex_timeout);
+ fhandler_pty_slave::transfer_input (tty::to_nat, from_master,
+ get_ttyp (), input_available_event);
+ release_attach_mutex ();
+ }
+ ReleaseMutex (input_mutex);
+
+ line_edit_status status = line_edit (p, len, ti, &ret);
+ if (status > line_edit_signalled && status != line_edit_pipe_full)
+ ret = -1;
+ return ret;
+}
+
+void
+fhandler_pty_master::read (void *ptr, size_t& len)
+{
+ bg_check_types bg = bg_check (SIGTTIN);
+ if (bg <= bg_eof)
+ {
+ len = (size_t) bg;
+ return;
+ }
+ push_process_state process_state (PID_TTYIN);
+ len = (size_t) process_slave_output ((char *) ptr, len, pktmode);
+}
+
+int
+fhandler_pty_master::tcgetattr (struct termios *t)
+{
+ *t = cygwin_shared->tty[get_minor ()]->ti;
+ /* Workaround for rlwrap v0.40 or later */
+ if (get_ttyp ()->pcon_start)
+ t->c_lflag &= ~(ICANON | ECHO);
+ if (get_ttyp ()->pcon_activated)
+ t->c_iflag &= ~ICRNL;
+ return 0;
+}
+
+int
+fhandler_pty_master::tcsetattr (int, const struct termios *t)
+{
+ cygwin_shared->tty[get_minor ()]->ti = *t;
+ return 0;
+}
+
+int
+fhandler_pty_master::tcflush (int queue)
+{
+ int ret = 0;
+
+ termios_printf ("tcflush(%d) handle %p", queue, get_handle ());
+
+ if (queue == TCIFLUSH || queue == TCIOFLUSH)
+ ret = process_slave_output (NULL, OUT_BUFFER_SIZE, 0);
+ if (queue == TCOFLUSH || queue == TCIOFLUSH)
+ {
+ /* do nothing for now. */
+ }
+
+ termios_printf ("%d=tcflush(%d)", ret, queue);
+ return ret;
+}
+
+int
+fhandler_pty_master::ioctl (unsigned int cmd, void *arg)
+{
+ int res = fhandler_termios::ioctl (cmd, arg);
+ if (res <= 0)
+ return res;
+
+ switch (cmd)
+ {
+ case TIOCPKT:
+ pktmode = *(int *) arg;
+ break;
+ case TIOCGWINSZ:
+ *(struct winsize *) arg = get_ttyp ()->winsize;
+ break;
+ case TIOCSWINSZ:
+ if (get_ttyp ()->winsize.ws_row != ((struct winsize *) arg)->ws_row
+ || get_ttyp ()->winsize.ws_col != ((struct winsize *) arg)->ws_col
+ || get_ttyp ()->winsize.ws_ypixel != ((struct winsize *) arg)->ws_ypixel
+ || get_ttyp ()->winsize.ws_xpixel != ((struct winsize *) arg)->ws_xpixel
+ )
+ {
+ if (get_ttyp ()->pcon_activated && get_ttyp ()->nat_pipe_owner_pid)
+ resize_pseudo_console ((struct winsize *) arg);
+ get_ttyp ()->winsize = *(struct winsize *) arg;
+ get_ttyp ()->kill_pgrp (SIGWINCH);
+ }
+ break;
+ case TIOCGPGRP:
+ *((pid_t *) arg) = this->tcgetpgrp ();
+ break;
+ case TIOCSPGRP:
+ return this->tcsetpgrp ((pid_t) (intptr_t) arg);
+ case FIONREAD:
+ {
+ DWORD n;
+ if (!bytes_available (n))
+ {
+ set_errno (EINVAL);
+ return -1;
+ }
+ *(int *) arg = (int) n;
+ }
+ break;
+ default:
+ return fhandler_base::ioctl (cmd, arg);
+ }
+ return 0;
+}
+
+int
+fhandler_pty_master::ptsname_r (char *buf, size_t buflen)
+{
+ char tmpbuf[TTY_NAME_MAX];
+
+ __ptsname (tmpbuf, get_minor ());
+ if (buflen <= strlen (tmpbuf))
+ {
+ set_errno (ERANGE);
+ return ERANGE;
+ }
+ strcpy (buf, tmpbuf);
+ return 0;
+}
+
+void
+fhandler_pty_common::set_close_on_exec (bool val)
+{
+ // Cygwin processes will handle this specially on exec.
+ close_on_exec (val);
+}
+
+void
+fhandler_pty_slave::setup_locale (void)
+{
+ extern UINT __eval_codepage_from_internal_charset ();
+
+ if (!get_ttyp ()->term_code_page)
+ {
+ get_ttyp ()->term_code_page = __eval_codepage_from_internal_charset ();
+ acquire_attach_mutex (mutex_timeout);
+ SetConsoleCP (get_ttyp ()->term_code_page);
+ SetConsoleOutputCP (get_ttyp ()->term_code_page);
+ release_attach_mutex ();
+ }
+}
+
+bg_check_types
+fhandler_pty_slave::bg_check (int sig, bool dontsignal)
+{
+ reset_switch_to_nat_pipe ();
+ return fhandler_termios::bg_check (sig, dontsignal);
+}
+
+void
+fhandler_pty_slave::fixup_after_fork (HANDLE parent)
+{
+ create_invisible_console ();
+
+ // fork_fixup (parent, inuse, "inuse");
+ // fhandler_pty_common::fixup_after_fork (parent);
+ report_tty_counts (this, "inherited", "");
+}
+
+void
+fhandler_pty_slave::fixup_after_exec ()
+{
+ if (!close_on_exec ())
+ fixup_after_fork (NULL); /* No parent handle required. */
+
+ /* Hook Console API */
+#define DO_HOOK(module, name) \
+ if (!name##_Orig) \
+ { \
+ void *api = hook_api (module, #name, (void *) name##_Hooked); \
+ name##_Orig = (__typeof__ (name) *) api; \
+ /*if (api) system_printf (#name " hooked.");*/ \
+ }
+ /* CreateProcess() is hooked for GDB etc. */
+ DO_HOOK (NULL, CreateProcessA);
+ DO_HOOK (NULL, CreateProcessW);
+}
+
+/* This thread function handles the master control pipe. It waits for a
+ client to connect. Then it checks if the client process has permissions
+ to access the tty handles. If so, it opens the client process and
+ duplicates the handles into that process. If that fails, it sends a reply
+ with at least one handle set to NULL and an error code. Last but not
+ least, the client is disconnected and the thread waits for the next client.
+
+ A special case is when the master side of the tty is about to be closed.
+ The client side is the fhandler_pty_master::close function and it sends
+ a PID -1 in that case. A check is performed that the request to leave
+ really comes from the master process itself.
+
+ Since there's always only one pipe instance, there's a chance that clients
+ have to wait to connect to the master control pipe. Therefore the client
+ calls to CallNamedPipe should have a big enough timeout value. For now this
+ is 500ms. Hope that's enough. */
+
+/* The function pty_master_thread() should be static because the instance
+ is deleted if the master is dup()'ed and the original is closed. In
+ this case, dup()'ed instance still exists, therefore, master thread
+ is also still alive even though the instance has been deleted. As a
+ result, accesing member variables in this function causes access
+ violation. */
+
+DWORD
+fhandler_pty_master::pty_master_thread (const master_thread_param_t *p)
+{
+ bool exit = false;
+ GENERIC_MAPPING map = { EVENT_QUERY_STATE, EVENT_MODIFY_STATE, 0,
+ EVENT_QUERY_STATE | EVENT_MODIFY_STATE };
+ pipe_request req;
+ DWORD len;
+ security_descriptor sd;
+ HANDLE token;
+ PRIVILEGE_SET ps;
+ DWORD pid;
+ NTSTATUS status;
+
+ termios_printf ("Entered");
+ while (!exit && (ConnectNamedPipe (p->master_ctl, NULL)
+ || GetLastError () == ERROR_PIPE_CONNECTED))
+ {
+ pipe_reply repl = { NULL, NULL, NULL, NULL, NULL, NULL, 0 };
+ bool deimp = false;
+ NTSTATUS allow = STATUS_ACCESS_DENIED;
+ ACCESS_MASK access = EVENT_MODIFY_STATE;
+ HANDLE client = NULL;
+
+ if (!ReadFile (p->master_ctl, &req, sizeof req, &len, NULL))
+ {
+ termios_printf ("ReadFile, %E");
+ goto reply;
+ }
+ if (!GetNamedPipeClientProcessId (p->master_ctl, &pid))
+ pid = req.pid;
+ if (get_object_sd (p->input_available_event, sd))
+ {
+ termios_printf ("get_object_sd, %E");
+ goto reply;
+ }
+ cygheap->user.deimpersonate ();
+ deimp = true;
+ if (!ImpersonateNamedPipeClient (p->master_ctl))
+ {
+ termios_printf ("ImpersonateNamedPipeClient, %E");
+ goto reply;
+ }
+ status = NtOpenThreadToken (GetCurrentThread (), TOKEN_QUERY, TRUE,
+ &token);
+ if (!NT_SUCCESS (status))
+ {
+ termios_printf ("NtOpenThreadToken, %y", status);
+ SetLastError (RtlNtStatusToDosError (status));
+ goto reply;
+ }
+ len = sizeof ps;
+ status = NtAccessCheck (sd, token, access, &map, &ps, &len, &access,
+ &allow);
+ NtClose (token);
+ if (!NT_SUCCESS (status))
+ {
+ termios_printf ("NtAccessCheck, %y", status);
+ SetLastError (RtlNtStatusToDosError (status));
+ goto reply;
+ }
+ if (!RevertToSelf ())
+ {
+ termios_printf ("RevertToSelf, %E");
+ goto reply;
+ }
+ if (req.pid == (DWORD) -1) /* Request to finish thread. */
+ {
+ /* Check if the requesting process is the master process itself. */
+ if (pid == GetCurrentProcessId ())
+ exit = true;
+ goto reply;
+ }
+ if (NT_SUCCESS (allow))
+ {
+ client = OpenProcess (PROCESS_DUP_HANDLE, FALSE, pid);
+ if (!client)
+ {
+ termios_printf ("OpenProcess, %E");
+ goto reply;
+ }
+ if (!DuplicateHandle (GetCurrentProcess (), p->from_master_nat,
+ client, &repl.from_master_nat,
+ 0, TRUE, DUPLICATE_SAME_ACCESS))
+ {
+ termios_printf ("DuplicateHandle (from_master_nat), %E");
+ goto reply;
+ }
+ if (!DuplicateHandle (GetCurrentProcess (), p->from_master,
+ client, &repl.from_master,
+ 0, TRUE, DUPLICATE_SAME_ACCESS))
+ {
+ termios_printf ("DuplicateHandle (from_master), %E");
+ goto reply;
+ }
+ if (!DuplicateHandle (GetCurrentProcess (), p->to_master_nat,
+ client, &repl.to_master_nat,
+ 0, TRUE, DUPLICATE_SAME_ACCESS))
+ {
+ termios_printf ("DuplicateHandle (to_master_nat), %E");
+ goto reply;
+ }
+ if (!DuplicateHandle (GetCurrentProcess (), p->to_master,
+ client, &repl.to_master,
+ 0, TRUE, DUPLICATE_SAME_ACCESS))
+ {
+ termios_printf ("DuplicateHandle (to_master), %E");
+ goto reply;
+ }
+ if (!DuplicateHandle (GetCurrentProcess (), p->to_slave_nat,
+ client, &repl.to_slave_nat,
+ 0, TRUE, DUPLICATE_SAME_ACCESS))
+ {
+ termios_printf ("DuplicateHandle (to_slave_nat), %E");
+ goto reply;
+ }
+ if (!DuplicateHandle (GetCurrentProcess (), p->to_slave,
+ client, &repl.to_slave,
+ 0, TRUE, DUPLICATE_SAME_ACCESS))
+ {
+ termios_printf ("DuplicateHandle (to_slave), %E");
+ goto reply;
+ }
+ }
+reply:
+ repl.error = GetLastError ();
+ if (client)
+ CloseHandle (client);
+ if (deimp)
+ cygheap->user.reimpersonate ();
+ sd.free ();
+ termios_printf ("Reply: from %p, to %p, error %u",
+ repl.from_master_nat, repl.to_master_nat, repl.error );
+ if (!WriteFile (p->master_ctl, &repl, sizeof repl, &len, NULL))
+ termios_printf ("WriteFile, %E");
+ if (!DisconnectNamedPipe (p->master_ctl))
+ termios_printf ("DisconnectNamedPipe, %E");
+ }
+ termios_printf ("Leaving");
+ return 0;
+}
+
+static DWORD
+pty_master_thread (VOID *arg)
+{
+ fhandler_pty_master::master_thread_param_t p;
+ ((fhandler_pty_master *) arg)->get_master_thread_param (&p);
+ return fhandler_pty_master::pty_master_thread (&p);
+}
+
+/* The function pty_master_fwd_thread() should be static because the
+ instance is deleted if the master is dup()'ed and the original is
+ closed. In this case, dup()'ed instance still exists, therefore,
+ master forwarding thread is also still alive even though the instance
+ has been deleted. As a result, accesing member variables in this
+ function causes access violation. */
+
+DWORD
+fhandler_pty_master::pty_master_fwd_thread (const master_fwd_thread_param_t *p)
+{
+ DWORD rlen;
+ tmp_pathbuf tp;
+ char *outbuf = tp.c_get ();
+ char *mbbuf = tp.c_get ();
+ static mbstate_t mbp;
+
+ termios_printf ("Started.");
+ for (;;)
+ {
+ p->ttyp->fwd_last_time = GetTickCount ();
+ DWORD n;
+ p->ttyp->fwd_not_empty =
+ ::bytes_available (n, p->from_slave_nat) && n;
+ if (!ReadFile (p->from_slave_nat, outbuf, NT_MAX_PATH, &rlen, NULL))
+ {
+ termios_printf ("ReadFile for forwarding failed, %E");
+ break;
+ }
+ if (p->ttyp->stop_fwd_thread)
+ break;
+ ssize_t wlen = rlen;
+ char *ptr = outbuf;
+ if (p->ttyp->pcon_activated)
+ {
+ /* Avoid setting window title to "cygwin-console-helper.exe" */
+ int state = 0;
+ int start_at = 0;
+ for (DWORD i=0; i<rlen; i++)
+ if (outbuf[i] == '\033')
+ {
+ start_at = i;
+ state = 1;
+ continue;
+ }
+ else if ((state == 1 && outbuf[i] == ']') ||
+ (state == 2 && outbuf[i] == '0') ||
+ (state == 3 && outbuf[i] == ';'))
+ {
+ state ++;
+ continue;
+ }
+ else if (state == 4 && outbuf[i] == '\a')
+ {
+ const char *helper_str = "\\bin\\cygwin-console-helper.exe";
+ if (memmem (&outbuf[start_at], i + 1 - start_at,
+ helper_str, strlen (helper_str)))
+ {
+ memmove (&outbuf[start_at], &outbuf[i+1], rlen-i-1);
+ rlen = wlen = start_at + rlen - i - 1;
+ }
+ state = 0;
+ continue;
+ }
+ else if (outbuf[i] == '\a')
+ {
+ state = 0;
+ continue;
+ }
+
+ /* Remove CSI > Pm m */
+ state = 0;
+ start_at = 0;
+ for (DWORD i = 0; i < rlen; i++)
+ if (outbuf[i] == '\033')
+ {
+ start_at = i;
+ state = 1;
+ continue;
+ }
+ else if ((state == 1 && outbuf[i] == '[')
+ || (state == 2 && outbuf[i] == '>'))
+ {
+ state ++;
+ continue;
+ }
+ else if (state == 3 && (isdigit (outbuf[i]) || outbuf[i] == ';'))
+ continue;
+ else if (state == 3 && outbuf[i] == 'm')
+ {
+ memmove (&outbuf[start_at], &outbuf[i+1], rlen-i-1);
+ rlen = wlen = start_at + rlen - i - 1;
+ state = 0;
+ i = start_at - 1;
+ continue;
+ }
+ else
+ state = 0;
+
+ /* Remove OSC Ps ; ? BEL/ST */
+ for (DWORD i = 0; i < rlen; i++)
+ if (state == 0 && outbuf[i] == '\033')
+ {
+ start_at = i;
+ state = 1;
+ continue;
+ }
+ else if ((state == 1 && outbuf[i] == ']')
+ || (state == 2 && outbuf[i] == ';')
+ || (state == 3 && outbuf[i] == '?')
+ || (state == 4 && outbuf[i] == '\033'))
+ {
+ state ++;
+ continue;
+ }
+ else if (state == 2 && isdigit (outbuf[i]))
+ continue;
+ else if ((state == 4 && outbuf[i] == '\a')
+ || (state == 5 && outbuf[i] == '\\'))
+ {
+ memmove (&outbuf[start_at], &outbuf[i+1], rlen-i-1);
+ rlen = wlen = start_at + rlen - i - 1;
+ state = 0;
+ i = start_at - 1;
+ continue;
+ }
+ else
+ state = 0;
+
+ if (p->ttyp->term_code_page != CP_UTF8)
+ {
+ size_t nlen = NT_MAX_PATH;
+ convert_mb_str (p->ttyp->term_code_page, mbbuf, &nlen,
+ CP_UTF8, ptr, wlen, &mbp);
+
+ ptr = mbbuf;
+ wlen = rlen = nlen;
+ }
+
+ /* OPOST processing was already done in pseudo console,
+ so just write it to to_master. */
+ DWORD written;
+ while (rlen>0)
+ {
+ if (!WriteFile (p->to_master, ptr, wlen, &written, NULL))
+ {
+ termios_printf ("WriteFile for forwarding failed, %E");
+ break;
+ }
+ ptr += written;
+ wlen = (rlen -= written);
+ }
+ continue;
+ }
+
+ UINT cp_from;
+ pinfo pinfo_target = pinfo (p->ttyp->invisible_console_pid);
+ DWORD target_pid = 0;
+ if (pinfo_target)
+ target_pid = pinfo_target->dwProcessId;
+ if (target_pid)
+ {
+ /* Slave attaches to a different console than master.
+ Therefore reattach here. */
+ DWORD resume_pid =
+ attach_console_temporarily (target_pid, p->helper_pid);
+ cp_from = GetConsoleOutputCP ();
+ resume_from_temporarily_attach (resume_pid);
+ }
+ else
+ cp_from = GetConsoleOutputCP ();
+
+ if (p->ttyp->term_code_page != cp_from)
+ {
+ size_t nlen = NT_MAX_PATH;
+ convert_mb_str (p->ttyp->term_code_page, mbbuf, &nlen,
+ cp_from, ptr, wlen, &mbp);
+
+ ptr = mbbuf;
+ wlen = rlen = nlen;
+ }
+
+ WaitForSingleObject (p->output_mutex, mutex_timeout);
+ while (rlen>0)
+ {
+ if (!process_opost_output (p->to_master, ptr, wlen,
+ true /* disable output_stopped */,
+ p->ttyp, false))
+ {
+ termios_printf ("WriteFile for forwarding failed, %E");
+ break;
+ }
+ ptr += wlen;
+ wlen = (rlen -= wlen);
+ }
+ ReleaseMutex (p->output_mutex);
+ }
+ return 0;
+}
+
+static DWORD
+pty_master_fwd_thread (VOID *arg)
+{
+ fhandler_pty_master::master_fwd_thread_param_t p;
+ ((fhandler_pty_master *) arg)->get_master_fwd_thread_param (&p);
+ return fhandler_pty_master::pty_master_fwd_thread (&p);
+}
+
+inline static bool
+is_running_as_service (void)
+{
+ return check_token_membership (well_known_service_sid)
+ || cygheap->user.saved_sid () == well_known_system_sid;
+}
+
+bool
+fhandler_pty_master::setup ()
+{
+ int res;
+ security_descriptor sd;
+ SECURITY_ATTRIBUTES sa = { sizeof (SECURITY_ATTRIBUTES), NULL, TRUE };
+
+ /* Find an unallocated pty to use. */
+ /* Create a pipe for cygwin app (master to slave) simultaneously. */
+ int unit = cygwin_shared->tty.allocate (from_master, get_output_handle ());
+ if (unit < 0)
+ return false;
+
+ ProtectHandle1 (get_output_handle (), to_pty);
+
+ tty& t = *cygwin_shared->tty[unit];
+ _tc = (tty_min *) &t;
+
+ tcinit (true); /* Set termios information. Force initialization. */
+
+ const char *errstr = NULL;
+ DWORD pipe_mode = PIPE_NOWAIT;
+
+ if (!SetNamedPipeHandleState (get_output_handle (), &pipe_mode, NULL, NULL))
+ termios_printf ("can't set output_handle(%p) to non-blocking mode",
+ get_output_handle ());
+
+ /* Pipe for non-cygwin apps (slave to master) */
+ char pipename[sizeof ("ptyNNNN-from-master-nat")];
+ __small_sprintf (pipename, "pty%d-to-master-nat", unit);
+ res = fhandler_pipe::create (&sec_none, &from_slave_nat, &to_master_nat,
+ fhandler_pty_common::pipesize, pipename, 0);
+ if (res)
+ {
+ errstr = "output pipe for non-cygwin apps";
+ goto err;
+ }
+
+ /* Pipe for cygwin apps (slave to master) */
+ __small_sprintf (pipename, "pty%d-to-master", unit);
+ res = fhandler_pipe::create (&sec_none, &get_handle (), &to_master,
+ fhandler_pty_common::pipesize, pipename, 0);
+ if (res)
+ {
+ errstr = "output pipe";
+ goto err;
+ }
+
+ /* Pipe for non-cygwin apps (master to slave) */
+ __small_sprintf (pipename, "pty%d-from-master-nat", unit);
+ /* FILE_FLAG_OVERLAPPED is specified here in order to prevent
+ PeekNamedPipe() from blocking in transfer_input().
+ Accordig to the official document, in order to access the handle
+ opened with FILE_FLAG_OVERLAPPED, it is mandatory to pass the
+ OVERLAPP structure, but in fact, it seems that the access will
+ fallback to the blocking access if it is not specified. */
+ res = fhandler_pipe::create (&sec_none, &from_master_nat, &to_slave_nat,
+ fhandler_pty_common::pipesize, pipename,
+ FILE_FLAG_OVERLAPPED);
+ if (res)
+ {
+ errstr = "input pipe";
+ goto err;
+ }
+
+ ProtectHandle1 (get_handle (), from_pty);
+
+ __small_sprintf (pipename, "pty%d-echoloop", unit);
+ res = fhandler_pipe::create (&sec_none, &echo_r, &echo_w,
+ fhandler_pty_common::pipesize, pipename, 0);
+ if (res)
+ {
+ errstr = "echo pipe";
+ goto err;
+ }
+
+ /* Create security attribute. Default permissions are 0620. */
+ sd.malloc (sizeof (SECURITY_DESCRIPTOR));
+ RtlCreateSecurityDescriptor (sd, SECURITY_DESCRIPTOR_REVISION);
+ if (!create_object_sd_from_attribute (myself->uid, myself->gid,
+ S_IFCHR | S_IRUSR | S_IWUSR | S_IWGRP,
+ sd))
+ sa.lpSecurityDescriptor = (PSECURITY_DESCRIPTOR) sd;
+
+ /* Carefully check that the input_available_event didn't already exist.
+ This is a measure to make sure that the event security descriptor
+ isn't occupied by a malicious process. We must make sure that the
+ event's security descriptor is what we expect it to be. */
+ if (!(input_available_event = t.get_event (errstr = INPUT_AVAILABLE_EVENT,
+ &sa, TRUE))
+ || GetLastError () == ERROR_ALREADY_EXISTS)
+ goto err;
+
+ char buf[MAX_PATH];
+ errstr = shared_name (buf, OUTPUT_MUTEX, unit);
+ if (!(output_mutex = CreateMutex (&sa, FALSE, buf)))
+ goto err;
+
+ errstr = shared_name (buf, INPUT_MUTEX, unit);
+ if (!(input_mutex = CreateMutex (&sa, FALSE, buf)))
+ goto err;
+
+ errstr = shared_name (buf, PIPE_SW_MUTEX, unit);
+ if (!(pipe_sw_mutex = CreateMutex (&sa, FALSE, buf)))
+ goto err;
+
+ if (!attach_mutex)
+ attach_mutex = CreateMutex (&sa, FALSE, NULL);
+
+ /* Create master control pipe which allows the master to duplicate
+ the pty pipe handles to processes which deserve it. */
+ __small_sprintf (buf, "\\\\.\\pipe\\cygwin-%S-pty%d-master-ctl",
+ &cygheap->installation_key, unit);
+ master_ctl = CreateNamedPipe (buf, PIPE_ACCESS_DUPLEX
+ | FILE_FLAG_FIRST_PIPE_INSTANCE,
+ PIPE_WAIT | PIPE_TYPE_MESSAGE
+ | PIPE_READMODE_MESSAGE
+ | PIPE_REJECT_REMOTE_CLIENTS,
+ 1, 4096, 4096, 0, &sec_all_nih);
+ if (master_ctl == INVALID_HANDLE_VALUE)
+ {
+ errstr = "pty master control pipe";
+ goto err;
+ }
+
+ thread_param_copied_event = CreateEvent(NULL, FALSE, FALSE, NULL);
+ master_thread = new cygthread (::pty_master_thread, this, "ptym");
+ if (!master_thread)
+ {
+ errstr = "pty master control thread";
+ goto err;
+ }
+ WaitForSingleObject (thread_param_copied_event, INFINITE);
+
+ if (wincap.has_broken_attach_console ()
+ && _major (myself->ctty) == DEV_CONS_MAJOR
+ && !(!pinfo (myself->ppid) && getenv ("ConEmuPID")))
+ {
+ HANDLE hello = CreateEvent (&sec_none, true, false, NULL);
+ HANDLE goodbye = CreateEvent (&sec_none, true, false, NULL);
+ WCHAR cmd[MAX_PATH];
+ path_conv helper ("/bin/cygwin-console-helper.exe");
+ size_t len = helper.get_wide_win32_path_len ();
+ helper.get_wide_win32_path (cmd);
+ __small_swprintf (cmd + len, L" %p %p", hello, goodbye);
+
+ STARTUPINFOEXW si;
+ PROCESS_INFORMATION pi;
+ ZeroMemory (&si, sizeof (si));
+ si.StartupInfo.cb = sizeof (STARTUPINFOEXW);
+
+ SIZE_T bytesRequired;
+ InitializeProcThreadAttributeList (NULL, 1, 0, &bytesRequired);
+ si.lpAttributeList = (PPROC_THREAD_ATTRIBUTE_LIST)
+ HeapAlloc (GetProcessHeap (), 0, bytesRequired);
+ InitializeProcThreadAttributeList (si.lpAttributeList,
+ 1, 0, &bytesRequired);
+ HANDLE handles_to_inherit[] = {hello, goodbye};
+ UpdateProcThreadAttribute (si.lpAttributeList,
+ 0,
+ PROC_THREAD_ATTRIBUTE_HANDLE_LIST,
+ handles_to_inherit,
+ sizeof (handles_to_inherit),
+ NULL, NULL);
+ if (CreateProcessW (NULL, cmd, &sec_none, &sec_none,
+ TRUE, EXTENDED_STARTUPINFO_PRESENT,
+ NULL, NULL, &si.StartupInfo, &pi))
+ {
+ WaitForSingleObject (hello, INFINITE);
+ CloseHandle (hello);
+ CloseHandle (pi.hThread);
+ helper_goodbye = goodbye;
+ helper_pid = pi.dwProcessId;
+ helper_h_process = pi.hProcess;
+ }
+ else
+ {
+ CloseHandle (hello);
+ CloseHandle (goodbye);
+ }
+ DeleteProcThreadAttributeList (si.lpAttributeList);
+ HeapFree (GetProcessHeap (), 0, si.lpAttributeList);
+ }
+
+ master_fwd_thread = new cygthread (::pty_master_fwd_thread, this, "ptymf");
+ if (!master_fwd_thread)
+ {
+ errstr = "pty master forwarding thread";
+ goto err;
+ }
+ WaitForSingleObject (thread_param_copied_event, INFINITE);
+ CloseHandle (thread_param_copied_event);
+
+ t.set_from_master_nat (from_master_nat);
+ t.set_from_master (from_master);
+ t.set_to_master_nat (to_master_nat);
+ t.set_to_master (to_master);
+ t.set_to_slave_nat (to_slave_nat);
+ t.set_to_slave (get_output_handle ());
+ t.winsize.ws_col = 80;
+ t.winsize.ws_row = 25;
+ t.master_pid = myself->pid;
+
+ dev ().parse (DEV_PTYM_MAJOR, unit);
+
+ t.master_is_running_as_service = is_running_as_service ();
+
+ termios_printf ("this %p, pty%d opened - from_pty <%p,%p>, to_pty %p",
+ this, unit, from_slave_nat, get_handle (),
+ get_output_handle ());
+ return true;
+
+err:
+ __seterrno ();
+ close_maybe (from_slave_nat);
+ close_maybe (to_slave_nat);
+ close_maybe (get_handle ());
+ close_maybe (get_output_handle ());
+ close_maybe (input_available_event);
+ close_maybe (output_mutex);
+ close_maybe (input_mutex);
+ close_maybe (from_master_nat);
+ close_maybe (to_master_nat);
+ close_maybe (to_master);
+ close_maybe (echo_r);
+ close_maybe (echo_w);
+ close_maybe (master_ctl);
+ /* The from_master must be closed last so that the same pty is not
+ allocated before cleaning up the other corresponding instances. */
+ close_maybe (from_master);
+ termios_printf ("pty%d open failed - failed to create %s", unit, errstr);
+ return false;
+}
+
+void
+fhandler_pty_master::fixup_after_fork (HANDLE parent)
+{
+ DWORD wpid = GetCurrentProcessId ();
+ fhandler_pty_master *arch = (fhandler_pty_master *) archetype;
+ if (arch->dwProcessId != wpid)
+ {
+ tty& t = *get_ttyp ();
+ if (myself->pid == t.master_pid)
+ {
+ t.set_from_master_nat (arch->from_master_nat);
+ t.set_from_master (arch->from_master);
+ t.set_to_master_nat (arch->to_master_nat);
+ t.set_to_master (arch->to_master);
+ }
+ arch->dwProcessId = wpid;
+ }
+ from_master_nat = arch->from_master_nat;
+ from_master = arch->from_master;
+ to_master_nat = arch->to_master_nat;
+ to_master = arch->to_master;
+#if 0 /* Not sure if this is necessary. */
+ from_slave_nat = arch->from_slave_nat;
+ to_slave_nat = arch->to_slave_nat;
+#endif
+ report_tty_counts (this, "inherited master", "");
+}
+
+void
+fhandler_pty_master::fixup_after_exec ()
+{
+ if (!close_on_exec ())
+ fixup_after_fork (spawn_info->parent);
+ else
+ from_master_nat = from_master = to_master_nat = to_master =
+ from_slave_nat = to_slave_nat = NULL;
+}
+
+BOOL
+fhandler_pty_common::process_opost_output (HANDLE h, const void *ptr,
+ ssize_t& len, bool is_echo,
+ tty *ttyp, bool is_nonblocking)
+{
+ ssize_t towrite = len;
+ BOOL res = TRUE;
+ if (ttyp->ti.c_lflag & FLUSHO)
+ return res; /* Discard write data */
+ while (towrite)
+ {
+ if (!is_echo)
+ {
+ if (ttyp->output_stopped && is_nonblocking)
+ {
+ if (towrite < len)
+ break;
+ else
+ {
+ set_errno (EAGAIN);
+ len = -1;
+ return TRUE;
+ }
+ }
+ while (ttyp->output_stopped)
+ cygwait (10);
+ }
+
+ if (!(ttyp->ti.c_oflag & OPOST)) // raw output mode
+ {
+ DWORD n = MIN (OUT_BUFFER_SIZE, towrite);
+ res = WriteFile (h, ptr, n, &n, NULL);
+ if (!res)
+ break;
+ ptr = (char *) ptr + n;
+ towrite -= n;
+ }
+ else // post-process output
+ {
+ char outbuf[OUT_BUFFER_SIZE + 1];
+ char *buf = (char *)ptr;
+ DWORD n = 0;
+ ssize_t rc = 0;
+ while (n < OUT_BUFFER_SIZE && rc < towrite)
+ {
+ switch (buf[rc])
+ {
+ case '\r':
+ if ((ttyp->ti.c_oflag & ONOCR)
+ && ttyp->column == 0)
+ {
+ rc++;
+ continue;
+ }
+ if (ttyp->ti.c_oflag & OCRNL)
+ {
+ outbuf[n++] = '\n';
+ rc++;
+ }
+ else
+ {
+ outbuf[n++] = buf[rc++];
+ ttyp->column = 0;
+ }
+ break;
+ case '\n':
+ if (ttyp->ti.c_oflag & ONLCR)
+ {
+ outbuf[n++] = '\r';
+ ttyp->column = 0;
+ }
+ if (ttyp->ti.c_oflag & ONLRET)
+ ttyp->column = 0;
+ outbuf[n++] = buf[rc++];
+ break;
+ default:
+ outbuf[n++] = buf[rc++];
+ ttyp->column++;
+ break;
+ }
+ }
+ res = WriteFile (h, outbuf, n, &n, NULL);
+ if (!res)
+ break;
+ ptr = (char *) ptr + rc;
+ towrite -= rc;
+ }
+ }
+ len -= towrite;
+ return res;
+}
+
+ /* Pseudo console supprot is realized using a tricky technic.
+ PTY need the pseudo console handles, however, they cannot
+ be retrieved by normal procedure. Therefore, run a helper
+ process in a pseudo console and get them from the helper.
+ Slave process will attach to the pseudo console in the
+ helper process using AttachConsole(). */
+bool
+fhandler_pty_slave::setup_pseudoconsole ()
+{
+ /* If the legacy console mode is enabled, pseudo console seems
+ not to work as expected. To determine console mode, registry
+ key ForceV2 in HKEY_CURRENT_USER\Console is checked. */
+ reg_key reg (HKEY_CURRENT_USER, KEY_READ, L"Console", NULL);
+ if (reg.error ())
+ return false;
+ if (reg.get_dword (L"ForceV2", 1) == 0)
+ {
+ termios_printf ("Pseudo console is disabled "
+ "because the legacy console mode is enabled.");
+ return false;
+ }
+
+ HANDLE hpConIn, hpConOut;
+ if (get_ttyp ()->pcon_activated)
+ { /* The pseudo console is already activated. */
+ if (GetStdHandle (STD_INPUT_HANDLE) == get_handle ())
+ { /* Send CSI6n just for requesting transfer input. */
+ DWORD n;
+ WaitForSingleObject (input_mutex, mutex_timeout);
+ get_ttyp ()->req_xfer_input = true; /* indicates that this "ESC[6n"
+ is just for transfer input */
+ get_ttyp ()->pcon_start = true;
+ get_ttyp ()->pcon_start_pid = myself->pid;
+ WriteFile (get_output_handle (), "\033[6n", 4, &n, NULL);
+ ReleaseMutex (input_mutex);
+ while (get_ttyp ()->pcon_start_pid)
+ /* wait for completion of transfer_input() in master::write(). */
+ Sleep (1);
+ }
+ /* Attach to the pseudo console which already exits. */
+ HANDLE pcon_owner = OpenProcess (PROCESS_DUP_HANDLE, FALSE,
+ get_ttyp ()->nat_pipe_owner_pid);
+ if (pcon_owner == NULL)
+ return false;
+ DuplicateHandle (pcon_owner, get_ttyp ()->h_pcon_in,
+ GetCurrentProcess (), &hpConIn,
+ 0, TRUE, DUPLICATE_SAME_ACCESS);
+ DuplicateHandle (pcon_owner, get_ttyp ()->h_pcon_out,
+ GetCurrentProcess (), &hpConOut,
+ 0, TRUE, DUPLICATE_SAME_ACCESS);
+ CloseHandle (pcon_owner);
+ acquire_attach_mutex (mutex_timeout);
+ FreeConsole ();
+ AttachConsole (get_ttyp ()->nat_pipe_owner_pid);
+ init_console_handler (false);
+ release_attach_mutex ();
+ goto skip_create;
+ }
+
+ STARTUPINFOEXW si;
+ PROCESS_INFORMATION pi;
+ HANDLE hello, goodbye;
+ HANDLE hr, hw;
+ HPCON hpcon;
+
+ do
+ { /* Create new pseudo console */
+ COORD size = {
+ (SHORT) get_ttyp ()->winsize.ws_col,
+ (SHORT) get_ttyp ()->winsize.ws_row
+ };
+ const DWORD inherit_cursor = 1;
+ hpcon = NULL;
+ SetLastError (ERROR_SUCCESS);
+ HRESULT res = CreatePseudoConsole (size, get_handle_nat (),
+ get_output_handle_nat (),
+ inherit_cursor, &hpcon);
+ if (res != S_OK || GetLastError () == ERROR_PROC_NOT_FOUND)
+ {
+ if (res != S_OK)
+ system_printf ("CreatePseudoConsole() failed. %08x %08x\n",
+ GetLastError (), res);
+ goto fallback;
+ }
+
+ SIZE_T bytesRequired;
+ InitializeProcThreadAttributeList (NULL, 2, 0, &bytesRequired);
+ ZeroMemory (&si, sizeof (si));
+ si.StartupInfo.cb = sizeof (STARTUPINFOEXW);
+ si.lpAttributeList = (PPROC_THREAD_ATTRIBUTE_LIST)
+ HeapAlloc (GetProcessHeap (), 0, bytesRequired);
+ if (si.lpAttributeList == NULL)
+ goto cleanup_pseudo_console;
+ if (!InitializeProcThreadAttributeList (si.lpAttributeList,
+ 2, 0, &bytesRequired))
+ goto cleanup_heap;
+ if (!UpdateProcThreadAttribute (si.lpAttributeList,
+ 0,
+ PROC_THREAD_ATTRIBUTE_PSEUDOCONSOLE,
+ hpcon, sizeof (hpcon), NULL, NULL))
+
+ goto cleanup_heap;
+
+ hello = CreateEvent (&sec_none, true, false, NULL);
+ goodbye = CreateEvent (&sec_none, true, false, NULL);
+ CreatePipe (&hr, &hw, &sec_none, 0);
+
+ HANDLE handles_to_inherit[] = {hello, goodbye, hw};
+ if (!UpdateProcThreadAttribute (si.lpAttributeList,
+ 0,
+ PROC_THREAD_ATTRIBUTE_HANDLE_LIST,
+ handles_to_inherit,
+ sizeof (handles_to_inherit),
+ NULL, NULL))
+ goto cleanup_event_and_pipes;
+
+ /* Execute helper process */
+ WCHAR cmd[MAX_PATH];
+ path_conv helper ("/bin/cygwin-console-helper.exe");
+ size_t len = helper.get_wide_win32_path_len ();
+ helper.get_wide_win32_path (cmd);
+ __small_swprintf (cmd + len, L" %p %p %p", hello, goodbye, hw);
+ si.StartupInfo.dwFlags = STARTF_USESTDHANDLES;
+ si.StartupInfo.hStdInput = NULL;
+ si.StartupInfo.hStdOutput = NULL;
+ si.StartupInfo.hStdError = NULL;
+
+ get_ttyp ()->pcon_activated = true;
+ get_ttyp ()->pcon_start = true;
+ get_ttyp ()->pcon_start_pid = myself->pid;
+ if (!CreateProcessW (NULL, cmd, &sec_none, &sec_none,
+ TRUE, EXTENDED_STARTUPINFO_PRESENT,
+ NULL, NULL, &si.StartupInfo, &pi))
+ goto cleanup_event_and_pipes;
+
+ for (;;)
+ {
+ DWORD wait_result = WaitForSingleObject (hello, 500);
+ if (wait_result == WAIT_OBJECT_0)
+ break;
+ if (wait_result != WAIT_TIMEOUT)
+ goto cleanup_helper_with_hello;
+ DWORD exit_code;
+ if (!GetExitCodeProcess (pi.hProcess, &exit_code))
+ goto cleanup_helper_with_hello;
+ if (exit_code == STILL_ACTIVE)
+ continue;
+ if (exit_code != 0 ||
+ WaitForSingleObject (hello, 500) != WAIT_OBJECT_0)
+ goto cleanup_helper_with_hello;
+ break;
+ }
+ CloseHandle (hello);
+ CloseHandle (pi.hThread);
+
+ /* Duplicate pseudo console handles */
+ DWORD rlen;
+ char buf[64];
+ if (!ReadFile (hr, buf, sizeof (buf), &rlen, NULL))
+ goto cleanup_helper_process;
+ buf[rlen] = '\0';
+ sscanf (buf, "StdHandles=%p,%p", &hpConIn, &hpConOut);
+ if (!DuplicateHandle (pi.hProcess, hpConIn,
+ GetCurrentProcess (), &hpConIn, 0,
+ TRUE, DUPLICATE_SAME_ACCESS))
+ goto cleanup_helper_process;
+ if (!DuplicateHandle (pi.hProcess, hpConOut,
+ GetCurrentProcess (), &hpConOut, 0,
+ TRUE, DUPLICATE_SAME_ACCESS))
+ goto cleanup_pcon_in;
+
+ CloseHandle (hr);
+ CloseHandle (hw);
+ DeleteProcThreadAttributeList (si.lpAttributeList);
+ HeapFree (GetProcessHeap (), 0, si.lpAttributeList);
+
+ /* Attach to pseudo console */
+ acquire_attach_mutex (mutex_timeout);
+ FreeConsole ();
+ AttachConsole (pi.dwProcessId);
+ init_console_handler (false);
+ release_attach_mutex ();
+
+ /* Terminate helper process */
+ SetEvent (goodbye);
+ WaitForSingleObject (pi.hProcess, INFINITE);
+ CloseHandle (goodbye);
+ CloseHandle (pi.hProcess);
+ }
+ while (false);
+
+skip_create:
+ do
+ {
+ /* Fixup handles */
+ HANDLE orig_input_handle_nat = get_handle_nat ();
+ HANDLE orig_output_handle_nat = get_output_handle_nat ();
+ cygheap_fdenum cfd (false);
+ while (cfd.next () >= 0)
+ if (cfd->get_device () == get_device ())
+ {
+ fhandler_base *fh = cfd;
+ fhandler_pty_slave *ptys = (fhandler_pty_slave *) fh;
+ if (ptys->get_handle_nat () == orig_input_handle_nat)
+ ptys->set_handle_nat (hpConIn);
+ if (ptys->get_output_handle_nat () == orig_output_handle_nat)
+ ptys->set_output_handle_nat (hpConOut);
+ }
+ CloseHandle (orig_input_handle_nat);
+ CloseHandle (orig_output_handle_nat);
+ }
+ while (false);
+
+ if (!process_alive (get_ttyp ()->nat_pipe_owner_pid))
+ get_ttyp ()->nat_pipe_owner_pid = myself->exec_dwProcessId;
+
+ if (hpcon && nat_pipe_owner_self (get_ttyp ()->nat_pipe_owner_pid))
+ {
+ HPCON_INTERNAL *hp = (HPCON_INTERNAL *) hpcon;
+ get_ttyp ()->h_pcon_write_pipe = hp->hWritePipe;
+ get_ttyp ()->h_pcon_condrv_reference = hp->hConDrvReference;
+ get_ttyp ()->h_pcon_conhost_process = hp->hConHostProcess;
+ DuplicateHandle (GetCurrentProcess (), hpConIn,
+ GetCurrentProcess (), &get_ttyp ()->h_pcon_in,
+ 0, TRUE, DUPLICATE_SAME_ACCESS);
+ DuplicateHandle (GetCurrentProcess (), hpConOut,
+ GetCurrentProcess (), &get_ttyp ()->h_pcon_out,
+ 0, TRUE, DUPLICATE_SAME_ACCESS);
+ /* Discard the pseudo console handler container here.
+ Reconstruct it temporary when it is needed. */
+ HeapFree (GetProcessHeap (), 0, hp);
+ }
+
+ acquire_attach_mutex (mutex_timeout);
+ if (get_ttyp ()->previous_code_page)
+ SetConsoleCP (get_ttyp ()->previous_code_page);
+ if (get_ttyp ()->previous_output_code_page)
+ SetConsoleOutputCP (get_ttyp ()->previous_output_code_page);
+
+ if (get_ttyp ()->getpgid () == myself->pgid)
+ {
+ termios &t = get_ttyp ()->ti;
+ DWORD mode;
+ /* Set input mode */
+ GetConsoleMode (hpConIn, &mode);
+ mode &= ~(ENABLE_ECHO_INPUT | ENABLE_LINE_INPUT | ENABLE_PROCESSED_INPUT);
+ if (t.c_lflag & ECHO)
+ mode |= ENABLE_ECHO_INPUT;
+ if (t.c_lflag & ICANON)
+ mode |= ENABLE_LINE_INPUT;
+ if (mode & ENABLE_ECHO_INPUT && !(mode & ENABLE_LINE_INPUT))
+ /* This is illegal, so turn off the echo here, and fake it
+ when we read the characters */
+ mode &= ~ENABLE_ECHO_INPUT;
+ if (t.c_lflag & ISIG)
+ mode |= ENABLE_PROCESSED_INPUT;
+ SetConsoleMode (hpConIn, mode);
+ /* Set output mode */
+ GetConsoleMode (hpConOut, &mode);
+ mode &= ~DISABLE_NEWLINE_AUTO_RETURN;
+ if (!(t.c_oflag & OPOST) || !(t.c_oflag & ONLCR))
+ mode |= DISABLE_NEWLINE_AUTO_RETURN;
+ SetConsoleMode (hpConOut, mode);
+ }
+ release_attach_mutex ();
+
+ return true;
+
+cleanup_helper_with_hello:
+ CloseHandle (hello);
+ CloseHandle (pi.hThread);
+ goto cleanup_helper_process;
+cleanup_pcon_in:
+ CloseHandle (hpConIn);
+cleanup_helper_process:
+ SetEvent (goodbye);
+ WaitForSingleObject (pi.hProcess, INFINITE);
+ CloseHandle (pi.hProcess);
+ goto skip_close_hello;
+cleanup_event_and_pipes:
+ CloseHandle (hello);
+skip_close_hello:
+ get_ttyp ()->pcon_start = false;
+ get_ttyp ()->pcon_start_pid = 0;
+ get_ttyp ()->pcon_activated = false;
+ CloseHandle (goodbye);
+ CloseHandle (hr);
+ CloseHandle (hw);
+cleanup_heap:
+ HeapFree (GetProcessHeap (), 0, si.lpAttributeList);
+cleanup_pseudo_console:
+ if (hpcon)
+ {
+ HPCON_INTERNAL *hp = (HPCON_INTERNAL *) hpcon;
+ HANDLE tmp = hp->hConHostProcess;
+ ClosePseudoConsole (hpcon);
+ CloseHandle (tmp);
+ }
+fallback:
+ return false;
+}
+
+/* Find a process to which the ownership of nat pipe should be handed over */
+DWORD
+fhandler_pty_slave::get_winpid_to_hand_over (tty *ttyp,
+ DWORD force_switch_to)
+{
+ DWORD switch_to = 0;
+ if (force_switch_to)
+ {
+ switch_to = force_switch_to;
+ ttyp->setpgid (force_switch_to + MAX_PID);
+ }
+ else if (nat_pipe_owner_self (ttyp->nat_pipe_owner_pid))
+ {
+ /* Search another native process which attaches to the same console */
+ DWORD current_pid = myself->exec_dwProcessId ?: myself->dwProcessId;
+ switch_to = get_console_process_id (current_pid, false, true, true);
+ if (!switch_to)
+ switch_to = get_console_process_id (current_pid, false, true, false);
+ }
+ return switch_to;
+}
+
+void
+fhandler_pty_slave::hand_over_only (tty *ttyp, DWORD force_switch_to)
+{
+ if (nat_pipe_owner_self (ttyp->nat_pipe_owner_pid))
+ {
+ DWORD switch_to = get_winpid_to_hand_over (ttyp, force_switch_to);
+ if (switch_to)
+ /* The process switch_to takes over the ownership of the nat pipe. */
+ ttyp->nat_pipe_owner_pid = switch_to;
+ else
+ {
+ /* Abandon the ownership of the nat pipe */
+ ttyp->nat_pipe_owner_pid = 0;
+ ttyp->switch_to_nat_pipe = false;
+ }
+ }
+}
+
+/* The function close_pseudoconsole() should be static so that it can
+ be called even after the fhandler_pty_slave instance is deleted. */
+void
+fhandler_pty_slave::close_pseudoconsole (tty *ttyp, DWORD force_switch_to)
+{
+ DWORD switch_to = get_winpid_to_hand_over (ttyp, force_switch_to);
+ acquire_attach_mutex (mutex_timeout);
+ ttyp->previous_code_page = GetConsoleCP ();
+ ttyp->previous_output_code_page = GetConsoleOutputCP ();
+ release_attach_mutex ();
+ if (nat_pipe_owner_self (ttyp->nat_pipe_owner_pid))
+ { /* I am owner of the nat pipe. */
+ if (switch_to)
+ {
+ /* Change pseudo console owner to another process (switch_to). */
+ HANDLE new_owner =
+ OpenProcess (PROCESS_DUP_HANDLE, FALSE, switch_to);
+ HANDLE new_write_pipe = NULL;
+ HANDLE new_condrv_reference = NULL;
+ HANDLE new_conhost_process = NULL;
+ HANDLE new_pcon_in = NULL, new_pcon_out = NULL;
+ DuplicateHandle (GetCurrentProcess (),
+ ttyp->h_pcon_write_pipe,
+ new_owner, &new_write_pipe,
+ 0, FALSE, DUPLICATE_SAME_ACCESS);
+ DuplicateHandle (GetCurrentProcess (),
+ ttyp->h_pcon_condrv_reference,
+ new_owner, &new_condrv_reference,
+ 0, FALSE, DUPLICATE_SAME_ACCESS);
+ DuplicateHandle (GetCurrentProcess (),
+ ttyp->h_pcon_conhost_process,
+ new_owner, &new_conhost_process,
+ 0, FALSE, DUPLICATE_SAME_ACCESS);
+ DuplicateHandle (GetCurrentProcess (), ttyp->h_pcon_in,
+ new_owner, &new_pcon_in,
+ 0, TRUE, DUPLICATE_SAME_ACCESS);
+ DuplicateHandle (GetCurrentProcess (), ttyp->h_pcon_out,
+ new_owner, &new_pcon_out,
+ 0, TRUE, DUPLICATE_SAME_ACCESS);
+ CloseHandle (new_owner);
+ CloseHandle (ttyp->h_pcon_write_pipe);
+ CloseHandle (ttyp->h_pcon_condrv_reference);
+ CloseHandle (ttyp->h_pcon_conhost_process);
+ CloseHandle (ttyp->h_pcon_in);
+ CloseHandle (ttyp->h_pcon_out);
+ ttyp->nat_pipe_owner_pid = switch_to;
+ ttyp->h_pcon_write_pipe = new_write_pipe;
+ ttyp->h_pcon_condrv_reference = new_condrv_reference;
+ ttyp->h_pcon_conhost_process = new_conhost_process;
+ ttyp->h_pcon_in = new_pcon_in;
+ ttyp->h_pcon_out = new_pcon_out;
+ acquire_attach_mutex (mutex_timeout);
+ FreeConsole ();
+ pinfo p (myself->ppid);
+ if (!p || !AttachConsole (p->dwProcessId))
+ AttachConsole (ATTACH_PARENT_PROCESS);
+ init_console_handler (false);
+ release_attach_mutex ();
+ }
+ else
+ { /* Close pseudo console and abandon the ownership of the nat pipe. */
+ acquire_attach_mutex (mutex_timeout);
+ FreeConsole ();
+ pinfo p (myself->ppid);
+ if (!p || !AttachConsole (p->dwProcessId))
+ AttachConsole (ATTACH_PARENT_PROCESS);
+ init_console_handler (false);
+ release_attach_mutex ();
+ /* Reconstruct pseudo console handler container here for close */
+ HPCON_INTERNAL *hp =
+ (HPCON_INTERNAL *) HeapAlloc (GetProcessHeap (), 0,
+ sizeof (HPCON_INTERNAL));
+ hp->hWritePipe = ttyp->h_pcon_write_pipe;
+ hp->hConDrvReference = ttyp->h_pcon_condrv_reference;
+ hp->hConHostProcess = ttyp->h_pcon_conhost_process;
+ /* HeapFree() will be called in ClosePseudoConsole() */
+ ClosePseudoConsole ((HPCON) hp);
+ CloseHandle (ttyp->h_pcon_conhost_process);
+ ttyp->pcon_activated = false;
+ ttyp->switch_to_nat_pipe = false;
+ ttyp->nat_pipe_owner_pid = 0;
+ ttyp->pcon_start = false;
+ ttyp->pcon_start_pid = 0;
+ }
+ }
+ else
+ { /* Just detach from the pseudo console if I am not owner. */
+ acquire_attach_mutex (mutex_timeout);
+ FreeConsole ();
+ pinfo p (myself->ppid);
+ if (!p || !AttachConsole (p->dwProcessId))
+ AttachConsole (ATTACH_PARENT_PROCESS);
+ init_console_handler (false);
+ release_attach_mutex ();
+ }
+}
+
+static bool
+has_ansi_escape_sequences (const WCHAR *env)
+{
+ /* Retrieve TERM name */
+ const char *term = NULL;
+ char term_str[260];
+ if (env)
+ {
+ for (const WCHAR *p = env; *p != L'\0'; p += wcslen (p) + 1)
+ if (swscanf (p, L"TERM=%236s", term_str) == 1)
+ {
+ term = term_str;
+ break;
+ }
+ }
+ else
+ term = getenv ("TERM");
+
+ if (!term)
+ return false;
+
+ /* If cursor_home is not "\033[H", terminal is not supposed to
+ support ANSI escape sequences. */
+ char tinfo[260];
+ __small_sprintf (tinfo, "/usr/share/terminfo/%02x/%s", term[0], term);
+ path_conv path (tinfo);
+ WCHAR wtinfo[260];
+ path.get_wide_win32_path (wtinfo);
+ HANDLE h;
+ h = CreateFileW (wtinfo, GENERIC_READ, FILE_SHARE_READ,
+ NULL, OPEN_EXISTING, 0, NULL);
+ if (h == NULL)
+ return false;
+ char terminfo[4096];
+ DWORD n;
+ ReadFile (h, terminfo, sizeof (terminfo), &n, 0);
+ CloseHandle (h);
+
+ int num_size = 2;
+ if (*(int16_t *)terminfo == 01036 /* MAGIC2 */)
+ num_size = 4;
+ const int name_pos = 12; /* Position of terminal name */
+ const int name_size = *(int16_t *) (terminfo + 2);
+ const int bool_count = *(int16_t *) (terminfo + 4);
+ const int num_count = *(int16_t *) (terminfo + 6);
+ const int str_count = *(int16_t *) (terminfo + 8);
+ const int str_size = *(int16_t *) (terminfo + 10);
+ const int cursor_home = 12; /* cursor_home entry index */
+ if (cursor_home >= str_count)
+ return false;
+ int str_idx_pos = name_pos + name_size + bool_count + num_size * num_count;
+ if (str_idx_pos & 1)
+ str_idx_pos ++;
+ const int16_t *str_idx = (int16_t *) (terminfo + str_idx_pos);
+ const char *str_table = (const char *) (str_idx + str_count);
+ if (str_idx + cursor_home >= (int16_t *) (terminfo + n))
+ return false;
+ if (str_idx[cursor_home] == -1)
+ return false;
+ const char *cursor_home_str = str_table + str_idx[cursor_home];
+ if (cursor_home_str >= str_table + str_size)
+ return false;
+ if (cursor_home_str >= terminfo + n)
+ return false;
+ if (strcmp (cursor_home_str, "\033[H") != 0)
+ return false;
+ return true;
+}
+
+bool
+fhandler_pty_slave::term_has_pcon_cap (const WCHAR *env)
+{
+ if (get_ttyp ()->pcon_cap_checked)
+ return get_ttyp ()->has_csi6n;
+
+ DWORD n;
+ char buf[1024];
+ char *p;
+ int len;
+ int wait_cnt = 0;
+
+ /* Check if terminal has ANSI escape sequence. */
+ if (!has_ansi_escape_sequences (env))
+ goto maybe_dumb;
+
+ /* Check if terminal has CSI6n */
+ WaitForSingleObject (pipe_sw_mutex, INFINITE);
+ WaitForSingleObject (input_mutex, mutex_timeout);
+ /* Set pcon_activated and pcon_start so that the response
+ will sent to io_handle_nat rather than io_handle. */
+ get_ttyp ()->pcon_activated = true;
+ /* pcon_start will be cleared in master write() when CSI6n is responded. */
+ get_ttyp ()->pcon_start = true;
+ WriteFile (get_output_handle (), "\033[6n", 4, &n, NULL);
+ ReleaseMutex (input_mutex);
+ p = buf;
+ len = sizeof (buf) - 1;
+ do
+ {
+ if (::bytes_available (n, get_handle_nat ()) && n)
+ {
+ ReadFile (get_handle_nat (), p, len, &n, NULL);
+ p += n;
+ len -= n;
+ *p = '\0';
+ char *p1 = strrchr (buf, '\033');
+ int x, y;
+ char c;
+ if (p1 == NULL || sscanf (p1, "\033[%d;%d%c", &y, &x, &c) != 3
+ || c != 'R')
+ continue;
+ wait_cnt = 0;
+ break;
+ }
+ else if (++wait_cnt > 100) /* Timeout */
+ goto not_has_csi6n;
+ else
+ Sleep (1);
+ }
+ while (len);
+ get_ttyp ()->pcon_activated = false;
+ get_ttyp ()->nat_pipe_owner_pid = 0;
+ ReleaseMutex (pipe_sw_mutex);
+ if (len == 0)
+ goto not_has_csi6n;
+
+ get_ttyp ()->has_csi6n = true;
+ get_ttyp ()->pcon_cap_checked = true;
+
+ return true;
+
+not_has_csi6n:
+ WaitForSingleObject (input_mutex, mutex_timeout);
+ /* If CSI6n is not responded, pcon_start is not cleared
+ in master write(). Therefore, clear it here manually. */
+ get_ttyp ()->pcon_start = false;
+ get_ttyp ()->pcon_activated = false;
+ ReleaseMutex (input_mutex);
+ ReleaseMutex (pipe_sw_mutex);
+maybe_dumb:
+ get_ttyp ()->pcon_cap_checked = true;
+ return false;
+}
+
+void
+fhandler_pty_slave::create_invisible_console ()
+{
+ if (get_ttyp ()->need_invisible_console)
+ {
+ /* Detach from console device and create new invisible console. */
+ acquire_attach_mutex (mutex_timeout);
+ FreeConsole();
+ fhandler_console::need_invisible (true);
+ init_console_handler (false);
+ release_attach_mutex ();
+ get_ttyp ()->need_invisible_console = false;
+ get_ttyp ()->invisible_console_pid = myself->pid;
+ }
+ if (get_ttyp ()->invisible_console_pid
+ && !pinfo (get_ttyp ()->invisible_console_pid))
+ /* If primary slave process does not exist anymore,
+ this process becomes the primary. */
+ get_ttyp ()->invisible_console_pid = myself->pid;
+}
+
+void
+fhandler_pty_master::get_master_thread_param (master_thread_param_t *p)
+{
+ p->from_master_nat = from_master_nat;
+ p->from_master = from_master;
+ p->to_master_nat = to_master_nat;
+ p->to_master = to_master;
+ p->to_slave_nat = to_slave_nat;
+ p->to_slave = get_output_handle ();
+ p->master_ctl = master_ctl;
+ p->input_available_event = input_available_event;
+ SetEvent (thread_param_copied_event);
+}
+
+void
+fhandler_pty_master::get_master_fwd_thread_param (master_fwd_thread_param_t *p)
+{
+ p->to_master = to_master;
+ p->from_slave_nat = from_slave_nat;
+ p->output_mutex = output_mutex;
+ p->ttyp = get_ttyp ();
+ p->helper_pid = helper_pid;
+ SetEvent (thread_param_copied_event);
+}
+
+#define ALT_PRESSED (LEFT_ALT_PRESSED | RIGHT_ALT_PRESSED)
+#define CTRL_PRESSED (LEFT_CTRL_PRESSED | RIGHT_CTRL_PRESSED)
+void
+fhandler_pty_slave::transfer_input (tty::xfer_dir dir, HANDLE from, tty *ttyp,
+ HANDLE input_available_event)
+{
+ HANDLE to;
+ if (dir == tty::to_nat)
+ to = ttyp->to_slave_nat ();
+ else
+ to = ttyp->to_slave ();
+
+ pinfo p (ttyp->master_pid);
+ HANDLE pty_owner = OpenProcess (PROCESS_DUP_HANDLE, FALSE, p->dwProcessId);
+ if (pty_owner)
+ {
+ DuplicateHandle (pty_owner, to, GetCurrentProcess (), &to,
+ 0, TRUE, DUPLICATE_SAME_ACCESS);
+ CloseHandle (pty_owner);
+ }
+ else
+ {
+ char pipe[MAX_PATH];
+ __small_sprintf (pipe,
+ "\\\\.\\pipe\\cygwin-%S-pty%d-master-ctl",
+ &cygheap->installation_key, ttyp->get_minor ());
+ pipe_request req = { GetCurrentProcessId () };
+ pipe_reply repl;
+ DWORD len;
+ if (!CallNamedPipe (pipe, &req, sizeof req,
+ &repl, sizeof repl, &len, 500))
+ return; /* What can we do? */
+ if (dir == tty::to_nat)
+ to = repl.to_slave_nat;
+ else
+ to = repl.to_slave;
+ }
+
+ UINT cp_from = 0, cp_to = 0;
+
+ if (dir == tty::to_nat)
+ {
+ cp_from = ttyp->term_code_page;
+ if (ttyp->pcon_activated)
+ cp_to = CP_UTF8;
+ else
+ cp_to = GetConsoleCP ();
+ }
+ else
+ {
+ cp_from = GetConsoleCP ();
+ cp_to = ttyp->term_code_page;
+ }
+
+ tmp_pathbuf tp;
+ char *buf = tp.c_get ();
+
+ bool transfered = false;
+
+ if (dir == tty::to_cyg && ttyp->pcon_activated)
+ { /* from handle is console handle */
+ /* Reaches here for nat->cyg case with pcon activated. */
+ INPUT_RECORD r[INREC_SIZE];
+ DWORD n;
+ while (PeekConsoleInputA (from, r, INREC_SIZE, &n) && n)
+ {
+ ReadConsoleInputA (from, r, n, &n);
+ if (ttyp->discard_input)
+ continue;
+ int len = 0;
+ char *ptr = buf;
+ for (DWORD i = 0; i < n; i++)
+ if (r[i].EventType == KEY_EVENT && r[i].Event.KeyEvent.bKeyDown)
+ {
+ DWORD ctrl_key_state = r[i].Event.KeyEvent.dwControlKeyState;
+ if (r[i].Event.KeyEvent.uChar.AsciiChar)
+ {
+ if ((ctrl_key_state & ALT_PRESSED)
+ && r[i].Event.KeyEvent.uChar.AsciiChar <= 0x7f)
+ buf[len++] = '\033'; /* Meta */
+ buf[len++] = r[i].Event.KeyEvent.uChar.AsciiChar;
+ }
+ /* Allow Ctrl-Space to emit ^@ */
+ else if (r[i].Event.KeyEvent.wVirtualKeyCode == '2'
+ && (ctrl_key_state & CTRL_PRESSED)
+ && !(ctrl_key_state & ALT_PRESSED))
+ buf[len++] = '\0';
+ else
+ { /* arrow/function keys */
+ /* FIXME: The current code generates cygwin terminal
+ sequence rather than xterm sequence. */
+ char tmp[16];
+ const char *add =
+ fhandler_console::get_nonascii_key (r[i], tmp);
+ if (add)
+ {
+ strcpy (buf + len, add);
+ len += strlen (add);
+ }
+ }
+ }
+ if (cp_to != cp_from)
+ {
+ static mbstate_t mbp;
+ char *mbbuf = tp.c_get ();
+ size_t nlen = NT_MAX_PATH;
+ convert_mb_str (cp_to, mbbuf, &nlen, cp_from, buf, len, &mbp);
+ ptr = mbbuf;
+ len = nlen;
+ }
+ /* Call WriteFile() line by line */
+ char *p0 = ptr;
+ char *p_cr = (char *) memchr (p0, '\r', len - (p0 - ptr));
+ char *p_lf = (char *) memchr (p0, '\n', len - (p0 - ptr));
+ while (p_cr || p_lf)
+ {
+ char *p1 =
+ p_cr ? (p_lf ? ((p_cr + 1 == p_lf)
+ ? p_lf : min(p_cr, p_lf)) : p_cr) : p_lf;
+ *p1 = '\n';
+ n = p1 - p0 + 1;
+ if (n && WriteFile (to, p0, n, &n, NULL) && n)
+ transfered = true;
+ p0 = p1 + 1;
+ p_cr = (char *) memchr (p0, '\r', len - (p0 - ptr));
+ p_lf = (char *) memchr (p0, '\n', len - (p0 - ptr));
+ }
+ n = len - (p0 - ptr);
+ if (n && WriteFile (to, p0, n, &n, NULL) && n)
+ transfered = true;
+ }
+ }
+ else
+ { /* Reaches here when both cyg->nat and nat->cyg cases with
+ pcon not activated or cyg->nat case with pcon activated. */
+ DWORD bytes_in_pipe;
+ while (::bytes_available (bytes_in_pipe, from) && bytes_in_pipe)
+ {
+ DWORD n = MIN (bytes_in_pipe, NT_MAX_PATH);
+ ReadFile (from, buf, n, &n, NULL);
+ if (ttyp->discard_input)
+ continue;
+ char *ptr = buf;
+ if (dir == tty::to_nat)
+ {
+ char *p = buf;
+ if (ttyp->pcon_activated)
+ while ((p = (char *) memchr (p, '\n', n - (p - buf))))
+ *p = '\r';
+ else
+ while ((p = (char *) memchr (p, '\r', n - (p - buf))))
+ *p = '\n';
+ }
+ if (cp_to != cp_from)
+ {
+ static mbstate_t mbp;
+ char *mbbuf = tp.c_get ();
+ size_t nlen = NT_MAX_PATH;
+ convert_mb_str (cp_to, mbbuf, &nlen, cp_from, buf, n, &mbp);
+ ptr = mbbuf;
+ n = nlen;
+ }
+ if (n && WriteFile (to, ptr, n, &n, NULL) && n)
+ transfered = true;;
+ }
+ }
+
+ /* Fix input_available_event which indicates availability in cyg pipe. */
+ if (dir == tty::to_nat) /* all data is transfered to nat pipe,
+ so no data available in cyg pipe. */
+ ResetEvent (input_available_event);
+ else if (transfered) /* There is data transfered to cyg pipe. */
+ SetEvent (input_available_event);
+ ttyp->pty_input_state = dir;
+ ttyp->discard_input = false;
+}
+
+void
+fhandler_pty_slave::cleanup_before_exit ()
+{
+ if (myself->process_state & PID_NOTCYGWIN)
+ get_ttyp ()->wait_fwd ();
+}
+
+void
+fhandler_pty_slave::get_duplicated_handle_set (handle_set_t *p)
+{
+ DuplicateHandle (GetCurrentProcess (), get_handle_nat (),
+ GetCurrentProcess (), &p->from_master_nat,
+ 0, 0, DUPLICATE_SAME_ACCESS);
+ DuplicateHandle (GetCurrentProcess (), input_available_event,
+ GetCurrentProcess (), &p->input_available_event,
+ 0, 0, DUPLICATE_SAME_ACCESS);
+ DuplicateHandle (GetCurrentProcess (), input_mutex,
+ GetCurrentProcess (), &p->input_mutex,
+ 0, 0, DUPLICATE_SAME_ACCESS);
+ DuplicateHandle (GetCurrentProcess (), pipe_sw_mutex,
+ GetCurrentProcess (), &p->pipe_sw_mutex,
+ 0, 0, DUPLICATE_SAME_ACCESS);
+}
+
+void
+fhandler_pty_slave::close_handle_set (handle_set_t *p)
+{
+ CloseHandle (p->from_master_nat);
+ p->from_master_nat = NULL;
+ CloseHandle (p->input_available_event);
+ p->input_available_event = NULL;
+ CloseHandle (p->input_mutex);
+ p->input_mutex = NULL;
+ CloseHandle (p->pipe_sw_mutex);
+ p->pipe_sw_mutex = NULL;
+}
+
+void
+fhandler_pty_slave::setup_for_non_cygwin_app (bool nopcon, PWCHAR envblock,
+ bool stdin_is_ptys)
+{
+ if (disable_pcon || !term_has_pcon_cap (envblock))
+ nopcon = true;
+ WaitForSingleObject (pipe_sw_mutex, INFINITE);
+ /* Setting switch_to_nat_pipe is necessary even if pseudo console
+ will not be activated. */
+ fhandler_base *fh = ::cygheap->fdtab[0];
+ if (fh && fh->get_major () == DEV_PTYS_MAJOR)
+ {
+ fhandler_pty_slave *ptys = (fhandler_pty_slave *) fh;
+ ptys->get_ttyp ()->switch_to_nat_pipe = true;
+ if (!process_alive (ptys->get_ttyp ()->nat_pipe_owner_pid))
+ ptys->get_ttyp ()->nat_pipe_owner_pid = myself->exec_dwProcessId;
+ }
+ bool pcon_enabled = false;
+ if (!nopcon)
+ pcon_enabled = setup_pseudoconsole ();
+ ReleaseMutex (pipe_sw_mutex);
+ /* For pcon enabled case, transfer_input() is called in master::write() */
+ if (!pcon_enabled && get_ttyp ()->getpgid () == myself->pgid
+ && stdin_is_ptys && get_ttyp ()->pty_input_state_eq (tty::to_cyg))
+ {
+ WaitForSingleObject (input_mutex, mutex_timeout);
+ acquire_attach_mutex (mutex_timeout);
+ transfer_input (tty::to_nat, get_handle (), get_ttyp (),
+ input_available_event);
+ release_attach_mutex ();
+ ReleaseMutex (input_mutex);
+ }
+}
+
+void
+fhandler_pty_slave::cleanup_for_non_cygwin_app (handle_set_t *p, tty *ttyp,
+ bool stdin_is_ptys,
+ DWORD force_switch_to)
+{
+ ttyp->wait_fwd ();
+ if (ttyp->getpgid () == myself->pgid && stdin_is_ptys
+ && ttyp->pty_input_state_eq (tty::to_nat))
+ {
+ WaitForSingleObject (p->input_mutex, mutex_timeout);
+ acquire_attach_mutex (mutex_timeout);
+ transfer_input (tty::to_cyg, p->from_master_nat, ttyp,
+ p->input_available_event);
+ release_attach_mutex ();
+ ReleaseMutex (p->input_mutex);
+ }
+ WaitForSingleObject (p->pipe_sw_mutex, INFINITE);
+ if (ttyp->pcon_activated)
+ close_pseudoconsole (ttyp, force_switch_to);
+ else
+ hand_over_only (ttyp, force_switch_to);
+ ReleaseMutex (p->pipe_sw_mutex);
+}
+
+void
+fhandler_pty_slave::setpgid_aux (pid_t pid)
+{
+ reset_switch_to_nat_pipe ();
+
+ WaitForSingleObject (pipe_sw_mutex, INFINITE);
+ bool was_nat_fg = get_ttyp ()->nat_fg (tc ()->pgid);
+ bool nat_fg = get_ttyp ()->nat_fg (pid);
+ if (!was_nat_fg && nat_fg && get_ttyp ()->switch_to_nat_pipe
+ && get_ttyp ()->pty_input_state_eq (tty::to_cyg))
+ {
+ WaitForSingleObject (input_mutex, mutex_timeout);
+ acquire_attach_mutex (mutex_timeout);
+ transfer_input (tty::to_nat, get_handle (), get_ttyp (),
+ input_available_event);
+ release_attach_mutex ();
+ ReleaseMutex (input_mutex);
+ }
+ else if (was_nat_fg && !nat_fg && get_ttyp ()->switch_to_nat_pipe
+ && get_ttyp ()->pty_input_state_eq (tty::to_nat))
+ {
+ bool attach_restore = false;
+ HANDLE from = get_handle_nat ();
+ DWORD resume_pid = 0;
+ WaitForSingleObject (input_mutex, mutex_timeout);
+ if (get_ttyp ()->pcon_activated && get_ttyp ()->nat_pipe_owner_pid
+ && !get_console_process_id (get_ttyp ()->nat_pipe_owner_pid, true))
+ {
+ HANDLE pcon_owner = OpenProcess (PROCESS_DUP_HANDLE, FALSE,
+ get_ttyp ()->nat_pipe_owner_pid);
+ DuplicateHandle (pcon_owner, get_ttyp ()->h_pcon_in,
+ GetCurrentProcess (), &from,
+ 0, TRUE, DUPLICATE_SAME_ACCESS);
+ CloseHandle (pcon_owner);
+ DWORD target_pid = get_ttyp ()->nat_pipe_owner_pid;
+ resume_pid = attach_console_temporarily (target_pid, 0);
+ attach_restore = true;
+ }
+ else
+ acquire_attach_mutex (mutex_timeout);
+ transfer_input (tty::to_cyg, from, get_ttyp (), input_available_event);
+ if (attach_restore)
+ resume_from_temporarily_attach (resume_pid);
+ else
+ release_attach_mutex ();
+ ReleaseMutex (input_mutex);
+ }
+ ReleaseMutex (pipe_sw_mutex);
+}
+
+bool
+fhandler_pty_master::need_send_ctrl_c_event ()
+{
+ /* If pseudo console is activated, sending CTRL_C_EVENT to non-cygwin
+ apps will be done in pseudo console, therefore, sending it in
+ fhandler_pty_master::write() duplicates that event for non-cygwin
+ apps. So return false if pseudo console is activated. */
+ return !(to_be_read_from_nat_pipe () && get_ttyp ()->pcon_activated
+ && get_ttyp ()->pty_input_state == tty::to_nat);
+}
+
+void
+fhandler_pty_slave::release_ownership_of_nat_pipe (tty *ttyp,
+ fhandler_termios *fh)
+{
+ if (fh->get_major () == DEV_PTYM_MAJOR
+ && nat_pipe_owner_self (ttyp->nat_pipe_owner_pid))
+ {
+ fhandler_pty_master *ptym = (fhandler_pty_master *) fh;
+ WaitForSingleObject (ptym->pipe_sw_mutex, INFINITE);
+ if (ttyp->pcon_activated)
+ /* Do not acquire/release_attach_mutex() here because
+ it has done in fhandler_termios::process_sigs(). */
+ close_pseudoconsole (ttyp);
+ else
+ hand_over_only (ttyp);
+ ReleaseMutex (ptym->pipe_sw_mutex);
+ }
+}
+
+DWORD
+fhandler_pty_common::attach_console_temporarily (DWORD target_pid,
+ DWORD helper_pid)
+{
+ DWORD resume_pid = 0;
+ acquire_attach_mutex (mutex_timeout);
+ pinfo pinfo_resume (myself->ppid);
+ if (helper_pid)
+ resume_pid = helper_pid;
+ else if (pinfo_resume)
+ resume_pid = pinfo_resume->dwProcessId;
+ if (!resume_pid)
+ resume_pid = get_console_process_id (myself->dwProcessId, false);
+ bool console_exists = fhandler_console::exists ();
+ if (!console_exists || resume_pid)
+ {
+ FreeConsole ();
+ AttachConsole (target_pid);
+ init_console_handler (false);
+ }
+ return console_exists ? resume_pid : (DWORD) -1;
+}
+
+void
+fhandler_pty_common::resume_from_temporarily_attach (DWORD resume_pid)
+{
+ bool console_exists = (resume_pid != (DWORD) -1);
+ if (!console_exists || resume_pid)
+ {
+ FreeConsole ();
+ if (console_exists)
+ if (!resume_pid || !AttachConsole (resume_pid))
+ AttachConsole (ATTACH_PARENT_PROCESS);
+ init_console_handler (false);
+ }
+ release_attach_mutex ();
+}