From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: Received: (qmail 92931 invoked by alias); 14 Apr 2019 15:23:59 -0000 Mailing-List: contact cygwin-developers-help@cygwin.com; run by ezmlm Precedence: bulk List-Id: List-Subscribe: List-Archive: List-Post: List-Help: , Sender: cygwin-developers-owner@cygwin.com Mail-Followup-To: cygwin-developers@cygwin.com Received: (qmail 92922 invoked by uid 89); 14 Apr 2019 15:23:59 -0000 Authentication-Results: sourceware.org; auth=none X-Spam-SWARE-Status: No, score=-17.7 required=5.0 tests=AWL,BAYES_00,GIT_PATCH_0,GIT_PATCH_1,GIT_PATCH_2,GIT_PATCH_3,RCVD_IN_DNSWL_NONE autolearn=ham version=3.3.1 spammy=identity, longjmp, inherited, Wuninitialized X-HELO: conuserg-05.nifty.com Received: from conuserg-05.nifty.com (HELO conuserg-05.nifty.com) (210.131.2.72) by sourceware.org (qpsmtpd/0.93/v0.84-503-g423c35a) with ESMTP; Sun, 14 Apr 2019 15:23:53 +0000 Received: from localhost.localdomain (ntsitm424054.sitm.nt.ngn.ppp.infoweb.ne.jp [219.97.74.54]) (authenticated) by conuserg-05.nifty.com with ESMTP id x3EFNMpD024514; Mon, 15 Apr 2019 00:23:35 +0900 DKIM-Filter: OpenDKIM Filter v2.10.3 conuserg-05.nifty.com x3EFNMpD024514 DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=nifty.ne.jp; s=dec2015msa; t=1555255415; bh=gqrOrQHzD3oB4K/Xy9KaJfHyNzApruJ7eMbB29ZErMk=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=xLxqf+QUwsjPlMtOFGd7RcdAq/VsfVTH7PRWMNd4vfP+CFxazmhG6v7sab3ZBzVbV qNt/WDYp459Q6mYXBicKHAUWNFW5HTu2/qzQ3sD0pO1GKbVrpHUzVZ734ApaBsfBIS tHTeIzYAPXmlDOVcPS/erzzH1uyhuX1bNgOSRKFTcKpnYhTNiCZNhnQQpQcIbJhxx2 361BFk/fSYSaNOKjZkNqkvYNTK5gG289GNLoxXpMNf6EE+gSCvwhb1Jb5ZUEWApDKX ancwJY8ZEj3xw/bmBrydHH/7rIsD+sawzrUY4mSD6SR8c9qnK6ikrvGx9ENL7YAQTr vQ4WhWyOej6lQ== From: Takashi Yano To: cygwin-developers@cygwin.com Cc: Takashi Yano Subject: [PATCH v5 1/1] Cygwin: pty: add pseudo console support. Date: Sun, 14 Apr 2019 15:23:00 -0000 Message-Id: <20190414152316.1468-2-takashi.yano@nifty.ne.jp> In-Reply-To: <20190414152316.1468-1-takashi.yano@nifty.ne.jp> References: <20190412102047.669-1-takashi.yano@nifty.ne.jp> <20190414152316.1468-1-takashi.yano@nifty.ne.jp> X-IsSubscribed: yes X-SW-Source: 2019-04/txt/msg00037.txt.bz2 - Support pseudo console in PTY. Pseudo console is a new feature in Windows 10 1809, which provides console APIs on virtual terminal. With this patch, native console applications can work in PTY such as mintty, ssh, gnu screen or tmux. --- winsup/cygwin/dtable.cc | 57 +++ winsup/cygwin/fhandler.h | 42 +- winsup/cygwin/fhandler_console.cc | 32 ++ winsup/cygwin/fhandler_tty.cc | 684 ++++++++++++++++++++++++-- winsup/cygwin/fork.cc | 24 + winsup/cygwin/select.cc | 22 +- winsup/cygwin/spawn.cc | 55 +++ winsup/cygwin/strace.cc | 24 + winsup/cygwin/tty.cc | 7 + winsup/cygwin/tty.h | 23 +- winsup/utils/cygwin-console-helper.cc | 14 +- 11 files changed, 940 insertions(+), 44 deletions(-) diff --git a/winsup/cygwin/dtable.cc b/winsup/cygwin/dtable.cc index 636221a77..0aaddab95 100644 --- a/winsup/cygwin/dtable.cc +++ b/winsup/cygwin/dtable.cc @@ -147,6 +147,42 @@ dtable::get_debugger_info () void dtable::stdio_init () { +#if 0 /* Experimental code */ + /* This is workaround for the issue: + https://www.cygwin.com/ml/cygwin-developers/2019-04/msg00000.html + when pseudo console is enabled. */ + fhandler_console::need_invisible (); +#endif + + bool need_fixup_handle = false; + fhandler_pty_slave *ptys = NULL; + bool is_pty[3] = {false, false, false}; + for (int fd = 0; fd < 3; fd ++) + { + fhandler_base *fh = cygheap->fdtab[fd]; + if (fh && fh->get_major () == DEV_PTYS_MAJOR) + { + ptys = (fhandler_pty_slave *) fh; + if (ptys->getPseudoConsole ()) + { + is_pty[fd] = true; + bool attached = !!fhandler_console::get_console_process_id + (ptys->getHelperProcessId (), true); + if (!attached) + { + /* Not attached to pseudo console in fork() or spawn() + by some reason. This happens if the executable is + a windows GUI binary, such as mintty. */ + FreeConsole (); + AttachConsole (ptys->getHelperProcessId ()); + need_fixup_handle = true; + } + } + } + } + if (need_fixup_handle) + goto fixup_handle; + if (myself->cygstarted || ISSTATE (myself, PID_CYGPARENT)) { tty_min *t = cygwin_shared->tty.get_cttyp (); @@ -155,6 +191,27 @@ dtable::stdio_init () return; } +fixup_handle: + if (need_fixup_handle) + { + HANDLE h; + h = CreateFile ("CONIN$", GENERIC_READ, FILE_SHARE_READ, + NULL, OPEN_EXISTING, 0, 0); + if (is_pty[0]) + { + SetStdHandle (STD_INPUT_HANDLE, h); + ptys->set_handle (h); + } + h = CreateFile ("CONOUT$", GENERIC_WRITE, FILE_SHARE_WRITE, + NULL, OPEN_EXISTING, 0, 0); + if (is_pty[1]) + SetStdHandle (STD_OUTPUT_HANDLE, h); + if (is_pty[2]) + SetStdHandle (STD_ERROR_HANDLE, h); + if (is_pty[1] || is_pty[2]) + ptys->set_output_handle (h); + } + HANDLE in = GetStdHandle (STD_INPUT_HANDLE); HANDLE out = GetStdHandle (STD_OUTPUT_HANDLE); HANDLE err = GetStdHandle (STD_ERROR_HANDLE); diff --git a/winsup/cygwin/fhandler.h b/winsup/cygwin/fhandler.h index bc66377cd..c31ae7230 100644 --- a/winsup/cygwin/fhandler.h +++ b/winsup/cygwin/fhandler.h @@ -1994,6 +1994,7 @@ private: static bool need_invisible (); static void free_console (); static const char *get_nonascii_key (INPUT_RECORD& input_rec, char *); + static DWORD get_console_process_id (DWORD pid, bool match); fhandler_console (void *) {} @@ -2026,14 +2027,15 @@ class fhandler_pty_common: public fhandler_termios public: fhandler_pty_common () : fhandler_termios (), - output_mutex (NULL), - input_mutex (NULL), input_available_event (NULL) + output_mutex (NULL), input_mutex (NULL), + input_available_event (NULL), pcon_attached (false) { pc.file_attributes (FILE_ATTRIBUTE_NORMAL); } static const unsigned pipesize = 128 * 1024; HANDLE output_mutex, input_mutex; HANDLE input_available_event; + bool pcon_attached; bool use_archetype () const {return true;} DWORD __acquire_output_mutex (const char *fn, int ln, DWORD ms); @@ -2064,14 +2066,28 @@ class fhandler_pty_common: public fhandler_termios return fh; } + bool attach_pcon_in_fork (void) + { + return get_ttyp ()->attach_pcon_in_fork; + } + DWORD getHelperProcessId (void) + { + return get_ttyp ()->HelperProcessId; + } + HPCON getPseudoConsole (void) + { + return get_ttyp ()->hPseudoConsole; + } + protected: - BOOL process_opost_output (HANDLE h, const void *ptr, ssize_t& len, bool is_echo); + BOOL process_opost_output (HANDLE h, + const void *ptr, ssize_t& len, bool is_echo); }; class fhandler_pty_slave: public fhandler_pty_common { HANDLE inuse; // used to indicate that a tty is in use - HANDLE output_handle_cyg; + HANDLE output_handle_cyg, io_handle_cyg; /* Helper functions for fchmod and fchown. */ bool fch_open_handles (bool chown); @@ -2084,6 +2100,8 @@ class fhandler_pty_slave: public fhandler_pty_common void set_output_handle_cyg (HANDLE h) { output_handle_cyg = h; } HANDLE& get_output_handle_cyg () { return output_handle_cyg; } + void set_handle_cyg (HANDLE h) { io_handle_cyg = h; } + HANDLE& get_handle_cyg () { return io_handle_cyg; } int open (int flags, mode_t mode = 0); void open_setup (int flags); @@ -2124,6 +2142,14 @@ class fhandler_pty_slave: public fhandler_pty_common copyto (fh); return fh; } + void reset_switch_to_pcon (void); + void push_to_pcon_screenbuffer (const char *ptr, size_t len); + bool has_master_opened (void); + void mask_switch_to_pcon (bool mask) + { + get_ttyp ()->mask_switch_to_pcon = mask; + } + void fixup_after_attach (bool native_maybe); }; #define __ptsname(buf, unit) __small_sprintf ((buf), "/dev/pty%d", (unit)) @@ -2132,17 +2158,17 @@ class fhandler_pty_master: public fhandler_pty_common int pktmode; // non-zero if pty in a packet mode. HANDLE master_ctl; // Control socket for handle duplication cygthread *master_thread; // Master control thread - HANDLE from_master, to_master; + HANDLE from_master, to_master, from_slave, to_slave; HANDLE echo_r, echo_w; DWORD dwProcessId; // Owner of master handles - HANDLE io_handle_cyg, to_master_cyg; + HANDLE to_master_cyg, from_master_cyg; cygthread *master_fwd_thread; // Master forwarding thread public: HANDLE get_echo_handle () const { return echo_r; } - HANDLE& get_handle_cyg () { return io_handle_cyg; } /* Constructor */ fhandler_pty_master (int); + ~fhandler_pty_master (); DWORD pty_master_thread (); DWORD pty_master_fwd_thread (); @@ -2187,6 +2213,8 @@ public: copyto (fh); return fh; } + + bool setup_pseudoconsole (void); }; class fhandler_dev_null: public fhandler_base diff --git a/winsup/cygwin/fhandler_console.cc b/winsup/cygwin/fhandler_console.cc index e3656a33a..335467b0b 100644 --- a/winsup/cygwin/fhandler_console.cc +++ b/winsup/cygwin/fhandler_console.cc @@ -1028,6 +1028,19 @@ fhandler_console::close () CloseHandle (get_handle ()); CloseHandle (get_output_handle ()); + + /* If already attached to pseudo console, don't call free_console () */ + cygheap_fdenum cfd (false); + while (cfd.next () >= 0) + if (cfd->get_major () == DEV_PTYM_MAJOR || + cfd->get_major () == DEV_PTYS_MAJOR) + { + fhandler_pty_common *t = + (fhandler_pty_common *) (fhandler_base *) cfd; + if (get_console_process_id (t->getHelperProcessId (), true)) + return 0; + } + if (!have_execed) free_console (); return 0; @@ -3082,6 +3095,25 @@ fhandler_console::need_invisible () return b; } +DWORD +fhandler_console::get_console_process_id (DWORD pid, bool match) +{ + DWORD tmp; + int num = GetConsoleProcessList (&tmp, 1); + DWORD *list = (DWORD *) + HeapAlloc (GetProcessHeap (), 0, num * sizeof (DWORD)); + num = GetConsoleProcessList (list, num); + tmp = 0; + for (int i=0; i #include "cygwait.h" +#include "tls_pbuf.h" + +/* Not yet defined in Mingw-w64 */ +#ifndef ENABLE_VIRTUAL_TERMINAL_PROCESSING +#define ENABLE_VIRTUAL_TERMINAL_PROCESSING 0x0004 +#endif /* ENABLE_VIRTUAL_TERMINAL_PROCESSING */ +#ifndef DISABLE_NEWLINE_AUTO_RETURN +#define DISABLE_NEWLINE_AUTO_RETURN 0x0008 +#endif /* DISABLE_NEWLINE_AUTO_RETURN */ +#ifndef PROC_THREAD_ATTRIBUTE_PSEUDOCONSOLE +#define PROC_THREAD_ATTRIBUTE_PSEUDOCONSOLE 0x00020016 +#endif /* PROC_THREAD_ATTRIBUTE_PSEUDOCONSOLE */ + +extern "C" int sscanf (const char *, const char *, ...); +extern "C" int ttyname_r(int, char*, size_t); #define close_maybe(h) \ do { \ @@ -39,6 +54,7 @@ struct pipe_request { struct pipe_reply { HANDLE from_master; + HANDLE from_master_cyg; HANDLE to_master; HANDLE to_master_cyg; DWORD error; @@ -67,7 +83,7 @@ bytes_available (DWORD& n, HANDLE h) bool fhandler_pty_common::bytes_available (DWORD &n) { - return ::bytes_available (n, get_handle ()); + return ::bytes_available (n, get_handle_cyg ()); } #ifdef DEBUGGING @@ -235,7 +251,7 @@ fhandler_pty_master::process_slave_output (char *buf, size_t len, int pktmode_on /* Check echo pipe first. */ if (::bytes_available (echo_cnt, echo_r) && echo_cnt > 0) break; - if (!::bytes_available (n, get_handle_cyg ())) + if (!bytes_available (n)) goto err; if (n) break; @@ -296,7 +312,7 @@ fhandler_pty_master::process_slave_output (char *buf, size_t len, int pktmode_on goto err; } } - else if (!ReadFile (get_handle_cyg (), outbuf, rlen, &n, NULL)) + else if (!ReadFile (get_handle (), outbuf, rlen, &n, NULL)) { termios_printf ("ReadFile failed, %E"); goto err; @@ -340,11 +356,14 @@ fhandler_pty_slave::fhandler_pty_slave (int unit) int fhandler_pty_slave::open (int flags, mode_t) { - HANDLE pty_owner, from_master_local, to_master_local, to_master_cyg_local; + HANDLE pty_owner; + HANDLE from_master_local, from_master_cyg_local; + HANDLE to_master_local, to_master_cyg_local; HANDLE *handles[] = { &from_master_local, &input_available_event, &input_mutex, &inuse, &output_mutex, &to_master_local, &pty_owner, &to_master_cyg_local, + &from_master_cyg_local, NULL }; @@ -396,7 +415,7 @@ fhandler_pty_slave::open (int flags, mode_t) release_output_mutex (); } - if (!get_ttyp ()->from_master () || + if (!get_ttyp ()->from_master () || !get_ttyp ()->from_master_cyg () || !get_ttyp ()->to_master () || !get_ttyp ()->to_master_cyg ()) { errmsg = "pty handles have been closed"; @@ -441,6 +460,15 @@ fhandler_pty_slave::open (int flags, mode_t) __seterrno (); goto err_no_msg; } + if (!DuplicateHandle (pty_owner, get_ttyp ()->from_master_cyg (), + GetCurrentProcess (), &from_master_cyg_local, 0, TRUE, + DUPLICATE_SAME_ACCESS)) + { + termios_printf ("can't duplicate input from %u/%p, %E", + get_ttyp ()->master_pid, get_ttyp ()->from_master_cyg ()); + __seterrno (); + goto err_no_msg; + } if (!DuplicateHandle (pty_owner, get_ttyp ()->to_master (), GetCurrentProcess (), &to_master_local, 0, TRUE, DUPLICATE_SAME_ACCESS)) @@ -474,9 +502,11 @@ fhandler_pty_slave::open (int flags, mode_t) goto err; } from_master_local = repl.from_master; + from_master_cyg_local = repl.from_master_cyg; to_master_local = repl.to_master; to_master_cyg_local = repl.to_master_cyg; - if (!from_master_local || !to_master_local || !to_master_cyg_local) + if (!from_master_local || !from_master_cyg_local || + !to_master_local || !to_master_cyg_local) { SetLastError (repl.error); errmsg = "error duplicating pipes, %E"; @@ -484,17 +514,21 @@ fhandler_pty_slave::open (int flags, mode_t) } } VerifyHandle (from_master_local); + VerifyHandle (from_master_cyg_local); VerifyHandle (to_master_local); VerifyHandle (to_master_cyg_local); termios_printf ("duplicated from_master %p->%p from pty_owner", get_ttyp ()->from_master (), from_master_local); + termios_printf ("duplicated from_master_cyg %p->%p from pty_owner", + get_ttyp ()->from_master_cyg (), from_master_cyg_local); termios_printf ("duplicated to_master %p->%p from pty_owner", get_ttyp ()->to_master (), to_master_local); termios_printf ("duplicated to_master_cyg %p->%p from pty_owner", get_ttyp ()->to_master_cyg (), to_master_cyg_local); set_handle (from_master_local); + set_handle_cyg (from_master_cyg_local); set_output_handle (to_master_local); set_output_handle_cyg (to_master_cyg_local); @@ -548,11 +582,20 @@ fhandler_pty_slave::close () if (!ForceCloseHandle (get_output_handle_cyg ())) termios_printf ("CloseHandle (get_output_handle_cyg ()<%p>), %E", get_output_handle_cyg ()); + if (!ForceCloseHandle (get_handle_cyg ())) + termios_printf ("CloseHandle (get_handle_cyg ()<%p>), %E", + get_handle_cyg ()); if ((unsigned) myself->ctty == FHDEV (DEV_PTYS_MAJOR, get_minor ())) fhandler_console::free_console (); /* assumes that we are the last pty closer */ +#if 1 + if (pcon_attached) + init_console_handler (false); +#endif fhandler_pty_common::close (); if (!ForceCloseHandle (output_mutex)) termios_printf ("CloseHandle (output_mutex<%p>), %E", output_mutex); + if (pcon_attached) + get_ttyp ()->num_pcon_attached_slaves --; return 0; } @@ -596,6 +639,177 @@ fhandler_pty_slave::init (HANDLE h, DWORD a, mode_t) return ret; } +void +fhandler_pty_slave::reset_switch_to_pcon (void) +{ + if (get_ttyp ()->pcon_pid && + get_ttyp ()->pcon_pid != myself->pid && + kill (get_ttyp ()->pcon_pid, 0) == 0) + /* There is a process which is grabbing pseudo console. */ + return; + if (get_ttyp ()->switch_to_pcon && + get_ttyp ()->pcon_pid != myself->pid) + Sleep (20); + get_ttyp ()->pcon_pid = 0; + get_ttyp ()->switch_to_pcon = false; +} + +void +fhandler_pty_slave::push_to_pcon_screenbuffer (const char *ptr, size_t len) +{ + DWORD pidRestore = 0; + if (!fhandler_console::get_console_process_id (getHelperProcessId (), true)) + if (pcon_attached) + { + system_printf ("pcon_attach mismatch??????"); + pcon_attached = false; + } + /* If not attached pseudo console yet, try to attach temporally. */ + if (!pcon_attached) + { + if (has_master_opened ()) + return; + + pidRestore = + fhandler_console::get_console_process_id (GetCurrentProcessId (), + false); + /* If pidRestore is not set, give up to push. */ + if (!pidRestore) + return; + + FreeConsole (); + if (!AttachConsole (getHelperProcessId ())) + goto detach; + } + char *buf; + size_t nlen; + if (GetConsoleOutputCP () != 65001 && + strstr (setlocale (LC_CTYPE, NULL), "UTF-8")) + { + /* Code page conversion from UTF-8 to OEM code page */ + /* FIXME: The terminal code may not be UTF-8. */ + size_t wlen = + MultiByteToWideChar (CP_UTF8, 0, (char *)ptr, len, NULL, 0); + wchar_t *wbuf = (wchar_t *) + HeapAlloc (GetProcessHeap (), 0, wlen * sizeof (wchar_t)); + wlen = + MultiByteToWideChar (CP_UTF8, 0, (char *)ptr, len, wbuf, wlen); + nlen = WideCharToMultiByte (CP_OEMCP, 0, + wbuf, wlen, NULL, 0, NULL, NULL); + buf = (char *) HeapAlloc (GetProcessHeap (), 0, nlen); + nlen = WideCharToMultiByte (CP_OEMCP, 0, + wbuf, wlen, buf, nlen, NULL, NULL); + HeapFree (GetProcessHeap (), 0, wbuf); + } + else + { + /* Just copy */ + buf = (char *) HeapAlloc (GetProcessHeap (), 0, len); + memcpy (buf, (char *)ptr, len); + nlen = len; + } + /* Remove alternate screen buffer drawing */ + char *p0, *p1; + p0 = p1 = buf; + while (p0 && p1) + { + if (!get_ttyp ()->screen_alternated) + { + /* Check switching to alternate screen buffer */ + p0 = (char *) memmem (p1, nlen - (p1-buf), "\033[?1049h", 8); + if (p0) + { + //p0 += 8; + get_ttyp ()->screen_alternated = true; + } + } + if (get_ttyp ()->screen_alternated) + { + /* Check switching to main screen buffer */ + p1 = (char *) memmem (p0, nlen - (p0-buf), "\033[?1049l", 8); + if (p1) + { + p1 += 8; + get_ttyp ()->screen_alternated = false; + memmove (p0, p1, buf+nlen - p1); + nlen -= p1 - p0; + } + else + nlen = p0 - buf; + } + } + if (!nlen) /* Nothing to be synchronized */ + goto cleanup; + if (get_ttyp ()->switch_to_pcon) + goto cleanup; + /* Remove ESC sequence which returns results to console + input buffer. Without this, cursor position report + is put into the input buffer as a garbage. */ + /* Remove ESC sequence to report cursor position. */ + while ((p0 = (char *) memmem (buf, nlen, "\033[6n", 4))) + { + memmove (p0, p0+4, nlen - (p0+4 - buf)); + nlen -= 4; + } + /* Remove ESC sequence to report terminal identity. */ + while ((p0 = (char *) memmem (buf, nlen, "\033[0c", 4))) + { + memmove (p0, p0+4, nlen - (p0+4 - buf)); + nlen -= 4; + } + DWORD dwMode, flags; + flags = ENABLE_VIRTUAL_TERMINAL_PROCESSING; + GetConsoleMode (get_output_handle (), &dwMode); + if (!(get_ttyp ()->ti.c_oflag & OPOST) || + !(get_ttyp ()->ti.c_oflag & ONLCR)) + flags |= DISABLE_NEWLINE_AUTO_RETURN; + SetConsoleMode (get_output_handle (), dwMode | flags); + char *p; + p = buf; + DWORD wLen, written; + written = 0; + int retry_count; + retry_count = 0; + while (written < nlen) + { + if (!WriteFile (get_output_handle (), p, nlen - written, &wLen, NULL)) + { + termios_printf ("WriteFile failed, %E"); + this->open (0, 0); /* Re-open handles */ + /* Fix pseudo console window size */ + struct winsize win; + this->ioctl (TIOCGWINSZ, &win); + this->ioctl (TIOCSWINSZ, &win); + if (++retry_count > 3) + break; + } + written += wLen; + p += wLen; + } + /* Detach from pseudo console and resume. */ + SetConsoleMode (get_output_handle (), dwMode); +cleanup: + HeapFree (GetProcessHeap (), 0, buf); +detach: + if (!pcon_attached) + { + FreeConsole (); + if (!AttachConsole (pidRestore)) + pcon_attached = false; + } +} + +bool +fhandler_pty_slave::has_master_opened (void) +{ + cygheap_fdenum cfd (false); + while (cfd.next () >= 0) + if (cfd->get_major () == DEV_PTYM_MAJOR && + cfd->get_minor () == get_minor ()) + return true; + return false; +} + ssize_t __stdcall fhandler_pty_slave::write (const void *ptr, size_t len) { @@ -609,6 +823,16 @@ fhandler_pty_slave::write (const void *ptr, size_t len) push_process_state process_state (PID_TTYOU); + reset_switch_to_pcon (); + + /* Push slave output to pseudo console screen buffer */ + if (getPseudoConsole ()) + { + acquire_output_mutex (INFINITE); + push_to_pcon_screenbuffer ((char *)ptr, len); + release_output_mutex (); + } + if (!process_opost_output (get_output_handle_cyg (), ptr, towrite, false)) { DWORD err = GetLastError (); @@ -629,6 +853,7 @@ fhandler_pty_slave::write (const void *ptr, size_t len) void __reg3 fhandler_pty_slave::read (void *ptr, size_t& len) { + char *ptr0 = (char *)ptr; ssize_t totalread = 0; int vmin = 0; int vtime = 0; /* Initialized to prevent -Wuninitialized warning */ @@ -644,10 +869,13 @@ fhandler_pty_slave::read (void *ptr, size_t& len) return; } - termios_printf ("read(%p, %lu) handle %p", ptr, len, get_handle ()); + termios_printf ("read(%p, %lu) handle %p", ptr, len, get_handle_cyg ()); push_process_state process_state (PID_TTYIN); + mask_switch_to_pcon (true); + reset_switch_to_pcon (); + if (is_nonblocking () || !ptr) /* Indicating tcflush(). */ time_to_wait = 0; else if ((get_ttyp ()->ti.c_lflag & ICANON)) @@ -757,6 +985,7 @@ fhandler_pty_slave::read (void *ptr, size_t& len) if (ptr && !bytes_in_pipe && !vmin && !time_to_wait) { ReleaseMutex (input_mutex); + mask_switch_to_pcon (false); len = (size_t) bytes_in_pipe; return; } @@ -777,7 +1006,7 @@ fhandler_pty_slave::read (void *ptr, size_t& len) if (readlen) { termios_printf ("reading %lu bytes (vtime %d)", readlen, vtime); - if (!ReadFile (get_handle (), buf, readlen, &n, NULL)) + if (!ReadFile (get_handle_cyg (), buf, readlen, &n, NULL)) { termios_printf ("read failed, %E"); ReleaseMutex (input_mutex); @@ -861,6 +1090,16 @@ fhandler_pty_slave::read (void *ptr, size_t& len) out: termios_printf ("%d = read(%p, %lu)", totalread, ptr, len); len = (size_t) totalread; +#if 1 /* Experimenta code */ + /* Push slave read as echo to pseudo console screen buffer. */ + if (getPseudoConsole () && ptr0 && (get_ttyp ()->ti.c_lflag & ECHO)) + { + acquire_output_mutex (INFINITE); + push_to_pcon_screenbuffer (ptr0, len); + release_output_mutex (); + } +#endif + mask_switch_to_pcon (false); } int @@ -890,6 +1129,7 @@ fhandler_pty_master::dup (fhandler_base *child, int) int fhandler_pty_slave::tcgetattr (struct termios *t) { + reset_switch_to_pcon (); *t = get_ttyp ()->ti; return 0; } @@ -897,6 +1137,7 @@ fhandler_pty_slave::tcgetattr (struct termios *t) int fhandler_pty_slave::tcsetattr (int, const struct termios *t) { + reset_switch_to_pcon (); acquire_output_mutex (INFINITE); get_ttyp ()->ti = *t; release_output_mutex (); @@ -908,7 +1149,9 @@ fhandler_pty_slave::tcflush (int queue) { int ret = 0; - termios_printf ("tcflush(%d) handle %p", queue, get_handle ()); + termios_printf ("tcflush(%d) handle %p", queue, get_handle_cyg ()); + + reset_switch_to_pcon (); if (queue == TCIFLUSH || queue == TCIOFLUSH) { @@ -929,6 +1172,7 @@ int fhandler_pty_slave::ioctl (unsigned int cmd, void *arg) { termios_printf ("ioctl (%x)", cmd); + reset_switch_to_pcon (); int res = fhandler_termios::ioctl (cmd, arg); if (res <= 0) return res; @@ -995,6 +1239,54 @@ fhandler_pty_slave::ioctl (unsigned int cmd, void *arg) get_ttyp ()->winsize = get_ttyp ()->arg.winsize; break; case TIOCSWINSZ: + if (getPseudoConsole ()) + { + /* If not attached pseudo console yet, try to attach + temporally. */ + DWORD pidRestore = 0; + if (!pcon_attached) + { + if (has_master_opened ()) + goto resize_cyg; + + pidRestore = fhandler_console::get_console_process_id + (GetCurrentProcessId (), false); + + /* This happens at mintty startup if fhandler_console:: + need_invisible() is called in stdio_init() in dtable.cc */ + if (!pidRestore) /* Give up to resize pseudo console */ + goto resize_cyg; + + FreeConsole (); + if (!AttachConsole (getHelperProcessId ())) + goto cleanup; + } + COORD size; + size.X = ((struct winsize *) arg)->ws_col; + size.Y = ((struct winsize *) arg)->ws_row; + CONSOLE_SCREEN_BUFFER_INFO csbi; + if (GetConsoleScreenBufferInfo (get_output_handle (), &csbi)) + if (size.X == csbi.srWindow.Right - csbi.srWindow.Left + 1 && + size.Y == csbi.srWindow.Bottom - csbi.srWindow.Top + 1) + goto cleanup; + if (!SetConsoleScreenBufferSize (get_output_handle (), size)) + goto cleanup; + SMALL_RECT rect; + rect.Left = 0; + rect.Top = 0; + rect.Right = size.X-1; + rect.Bottom = size.Y-1; + SetConsoleWindowInfo (get_output_handle (), TRUE, &rect); +cleanup: + /* Detach from pseudo console and resume. */ + if (pidRestore) + { + FreeConsole (); + if (!AttachConsole (pidRestore)) + pcon_attached = false; + } + } +resize_cyg: if (get_ttyp ()->winsize.ws_row != ((struct winsize *) arg)->ws_row || get_ttyp ()->winsize.ws_col != ((struct winsize *) arg)->ws_col) { @@ -1228,8 +1520,9 @@ errout: fhandler_pty_master::fhandler_pty_master (int unit) : fhandler_pty_common (), pktmode (0), master_ctl (NULL), master_thread (NULL), from_master (NULL), to_master (NULL), - echo_r (NULL), echo_w (NULL), dwProcessId (0), - io_handle_cyg (NULL), to_master_cyg (NULL), master_fwd_thread (NULL) + from_slave (NULL), to_slave (NULL), echo_r (NULL), echo_w (NULL), + dwProcessId (0), to_master_cyg (NULL), from_master_cyg (NULL), + master_fwd_thread (NULL) { if (unit >= 0) dev ().parse (DEV_PTYM_MAJOR, unit); @@ -1241,6 +1534,12 @@ fhandler_pty_master::fhandler_pty_master (int unit) set_name ("/dev/ptmx"); } +fhandler_pty_master::~fhandler_pty_master () +{ + if (to_master && from_master) + close (); +} + int fhandler_pty_master::open (int flags, mode_t) { @@ -1285,7 +1584,8 @@ fhandler_pty_master::cleanup () { report_tty_counts (this, "closing master", ""); if (archetype) - from_master = to_master = to_master_cyg = NULL; + from_master = from_master_cyg = + to_master = to_master_cyg = from_slave = to_slave = NULL; fhandler_base::cleanup (); } @@ -1294,9 +1594,11 @@ fhandler_pty_master::close () { OBJECT_BASIC_INFORMATION obi; NTSTATUS status; + pid_t master_pid_tmp = get_ttyp ()->master_pid; - termios_printf ("closing from_master(%p)/to_master(%p)/to_master_cyg(%p) since we own them(%u)", - from_master, to_master, to_master_cyg, dwProcessId); + termios_printf ("closing from_master(%p)/from_master_cyg(%p)/to_master(%p)/to_master_cyg(%p) since we own them(%u)", + from_master, from_master_cyg, + to_master, to_master_cyg, dwProcessId); if (cygwin_finished_initializing) { if (master_ctl && get_ttyp ()->master_pid == myself->pid) @@ -1334,9 +1636,30 @@ fhandler_pty_master::close () termios_printf ("CloseHandle (output_mutex<%p>), %E", output_mutex); if (!NT_SUCCESS (status)) debug_printf ("NtQueryObject: %y", status); - else if (obi.HandleCount == 1) + else if (obi.HandleCount == (getPseudoConsole () ? 2 : 1)) + /* Helper process has inherited one. */ { termios_printf("Closing last master of pty%d", get_minor ()); + /* Close Pseudo Console */ + if (getPseudoConsole ()) + { + /* Terminate helper process */ + SetEvent (get_ttyp ()->hHelperGoodbye); + WaitForSingleObject (get_ttyp ()->hHelperProcess, INFINITE); + /* FIXME: Pseudo console can be accessed via its handle + only in the process which created it. What else can we do? */ + if (master_pid_tmp == myself->pid) + { + /* Release pseudo console */ + HMODULE hModule = GetModuleHandle ("kernel32.dll"); + FARPROC func = GetProcAddress (hModule, "ClosePseudoConsole"); + VOID (WINAPI *ClosePseudoConsole) (HPCON) = NULL; + ClosePseudoConsole = (VOID (WINAPI *) (HPCON)) func; + ClosePseudoConsole (getPseudoConsole ()); + } + get_ttyp ()->hPseudoConsole = NULL; + get_ttyp ()->switch_to_pcon = false; + } if (get_ttyp ()->getsid () > 0) kill (get_ttyp ()->getsid (), SIGHUP); SetEvent (input_available_event); @@ -1344,17 +1667,24 @@ fhandler_pty_master::close () if (!ForceCloseHandle (from_master)) termios_printf ("error closing from_master %p, %E", from_master); + if (from_master_cyg != from_master) /* Avoid double close. */ + if (!ForceCloseHandle (from_master_cyg)) + termios_printf ("error closing from_master_cyg %p, %E", from_master_cyg); if (!ForceCloseHandle (to_master)) termios_printf ("error closing to_master %p, %E", to_master); from_master = to_master = NULL; - if (!ForceCloseHandle (get_handle_cyg ())) - termios_printf ("error closing io_handle_cyg %p, %E", get_handle_cyg ()); + if (!ForceCloseHandle (from_slave)) + termios_printf ("error closing from_slave %p, %E", from_slave); + from_slave = NULL; if (!ForceCloseHandle (to_master_cyg)) termios_printf ("error closing to_master_cyg %p, %E", to_master_cyg); - get_handle_cyg () = to_master_cyg = NULL; + to_master_cyg = from_master_cyg = NULL; ForceCloseHandle (echo_r); ForceCloseHandle (echo_w); echo_r = echo_w = NULL; + if (to_slave) + ForceCloseHandle (to_slave); + to_slave = NULL; if (have_execed || get_ttyp ()->master_pid != myself->pid) termios_printf ("not clearing: %d, master_pid %d", have_execed, get_ttyp ()->master_pid); @@ -1376,6 +1706,17 @@ fhandler_pty_master::write (const void *ptr, size_t len) return (ssize_t) bg; push_process_state process_state (PID_TTYOU); + + /* Write terminal input to to_slave pipe instead of output_handle + if current application is native console application. */ + if (get_ttyp ()->switch_to_pcon && + !get_ttyp ()->mask_switch_to_pcon) + { + DWORD wLen; + WriteFile (to_slave, ptr, len, &wLen, NULL); + return wLen; + } + line_edit_status status = line_edit (p++, len, ti, &ret); if (status > line_edit_signalled && status != line_edit_pipe_full) ret = -1; @@ -1443,6 +1784,19 @@ fhandler_pty_master::ioctl (unsigned int cmd, void *arg) *(struct winsize *) arg = get_ttyp ()->winsize; break; case TIOCSWINSZ: + /* FIXME: Pseudo console can be accessed via its handle + only in the process which created it. What else can we do? */ + if (getPseudoConsole () && get_ttyp ()->master_pid == myself->pid) + { + HMODULE hModule = GetModuleHandle ("kernel32.dll"); + FARPROC func = GetProcAddress (hModule, "ResizePseudoConsole"); + HRESULT (WINAPI *ResizePseudoConsole) (HPCON, COORD) = NULL; + ResizePseudoConsole = (HRESULT (WINAPI *) (HPCON, COORD)) func; + COORD size; + size.X = ((struct winsize *) arg)->ws_col; + size.Y = ((struct winsize *) arg)->ws_row; + ResizePseudoConsole (getPseudoConsole (), size); + } if (get_ttyp ()->winsize.ws_row != ((struct winsize *) arg)->ws_row || get_ttyp ()->winsize.ws_col != ((struct winsize *) arg)->ws_col) { @@ -1458,7 +1812,7 @@ fhandler_pty_master::ioctl (unsigned int cmd, void *arg) case FIONREAD: { DWORD n; - if (!::bytes_available (n, get_handle_cyg ())) + if (!bytes_available (n)) { set_errno (EINVAL); return -1; @@ -1494,9 +1848,59 @@ fhandler_pty_common::set_close_on_exec (bool val) close_on_exec (val); } +void +fhandler_pty_slave::fixup_after_attach (bool native_maybe) +{ + if (getPseudoConsole ()) + { + if (fhandler_console::get_console_process_id (getHelperProcessId (), + true)) + { + if (!pcon_attached) + { + setlocale (LC_CTYPE, ""); +#if 1 + if (strstr (setlocale (LC_CTYPE, NULL), "UTF-8")) + { + /* FIXME: The terminal code may not be UTF-8. */ + SetConsoleCP (65001); + SetConsoleOutputCP (65001); + } +#endif +#if 1 /* Experimental code */ + /* Clear screen to synchronize pseudo console screen buffer + with real terminal. */ + /* FIXME: Clearing sequence may not be "^[[H^[[J" + depending on the terminal type. However, it is + "^[[H^[[J" for pseudo console. */ + DWORD n; + if (get_ttyp ()->num_pcon_attached_slaves == 0) + /* Assume this is the first process using this pty slave. */ + WriteFile (get_output_handle_cyg (), + "\033[H\033[J", 6, &n, NULL); +#endif + pcon_attached = true; + get_ttyp ()->num_pcon_attached_slaves ++; + } + } + else + pcon_attached = false; + } + if (pcon_attached && native_maybe) + get_ttyp ()->switch_to_pcon = true; + if (pcon_attached && native_maybe && + (get_ttyp ()->pcon_pid == 0 || kill (get_ttyp ()->pcon_pid, 0) != 0)) + get_ttyp ()->pcon_pid = myself->pid; +#if 1 + if (pcon_attached) + init_console_handler (true); +#endif +} + void fhandler_pty_slave::fixup_after_fork (HANDLE parent) { + fixup_after_attach (false); // fork_fixup (parent, inuse, "inuse"); // fhandler_pty_common::fixup_after_fork (parent); report_tty_counts (this, "inherited", ""); @@ -1507,6 +1911,11 @@ fhandler_pty_slave::fixup_after_exec () { if (!close_on_exec ()) fixup_after_fork (NULL); + if (get_ttyp ()->pcon_pid == myself->pid) + { + get_ttyp ()->switch_to_pcon = false; + get_ttyp ()->pcon_pid = 0; + } } /* This thread function handles the master control pipe. It waits for a @@ -1544,7 +1953,7 @@ fhandler_pty_master::pty_master_thread () while (!exit && (ConnectNamedPipe (master_ctl, NULL) || GetLastError () == ERROR_PIPE_CONNECTED)) { - pipe_reply repl = { NULL, NULL, 0 }; + pipe_reply repl = { NULL, NULL, NULL, 0 }; bool deimp = false; NTSTATUS allow = STATUS_ACCESS_DENIED; ACCESS_MASK access = EVENT_MODIFY_STATE; @@ -1614,6 +2023,13 @@ fhandler_pty_master::pty_master_thread () termios_printf ("DuplicateHandle (from_master), %E"); goto reply; } + if (!DuplicateHandle (GetCurrentProcess (), from_master_cyg, + client, &repl.from_master_cyg, + 0, TRUE, DUPLICATE_SAME_ACCESS)) + { + termios_printf ("DuplicateHandle (from_master_cyg), %E"); + goto reply; + } if (!DuplicateHandle (GetCurrentProcess (), to_master, client, &repl.to_master, 0, TRUE, DUPLICATE_SAME_ACCESS)) @@ -1659,23 +2075,78 @@ fhandler_pty_master::pty_master_fwd_thread () DWORD rlen; char outbuf[OUT_BUFFER_SIZE]; - termios_printf("Started."); + termios_printf ("Started."); for (;;) { - if (!ReadFile (get_handle (), outbuf, sizeof outbuf, &rlen, NULL)) + if (!ReadFile (from_slave, outbuf, sizeof outbuf, &rlen, NULL)) { termios_printf ("ReadFile for forwarding failed, %E"); break; } ssize_t wlen = rlen; + char *ptr = outbuf; + if (getPseudoConsole ()) + { + /* Avoid duplicating slave output which is already sent to + to_master_cyg */ + if (!get_ttyp ()->switch_to_pcon) + continue; + + /* Avoid setting window title to "cygwin-console-helper.exe" */ + int state = 0; + int start_at = 0; + for (DWORD i=0; i0) + { + if (!WriteFile (to_master_cyg, ptr, wlen, &written, NULL)) + { + termios_printf ("WriteFile for forwarding failed, %E"); + break; + } + ptr += written; + wlen = (rlen -= written); + } + continue; + } while (rlen>0) { - if (!process_opost_output (to_master_cyg, outbuf, wlen, false)) + if (!process_opost_output (to_master_cyg, ptr, wlen, false)) { termios_printf ("WriteFile for forwarding failed, %E"); break; } - rlen -= wlen; + ptr += wlen; + wlen = (rlen -= wlen); } } return 0; @@ -1687,6 +2158,141 @@ pty_master_fwd_thread (VOID *arg) return ((fhandler_pty_master *) arg)->pty_master_fwd_thread (); } +/* If master process is running as service, attaching to + pseudo console should be done in fork. If attaching + is done in spawn for inetd or sshd, it fails because + the helper process is running as privileged user while + slave process is not. This function is used to determine + if the process is running as a srvice or not. */ +static bool +is_running_as_service (void) +{ + DWORD dwSize = 0; + PTOKEN_GROUPS pGroupInfo; + tmp_pathbuf tp; + pGroupInfo = (PTOKEN_GROUPS) tp.w_get (); + NtQueryInformationToken (hProcToken, TokenGroups, pGroupInfo, + 2 * NT_MAX_PATH, &dwSize); + for (DWORD i=0; iGroupCount; i++) + if (RtlEqualSid (well_known_service_sid, pGroupInfo->Groups[i].Sid)) + return true; + for (DWORD i=0; iGroupCount; i++) + if (RtlEqualSid (well_known_interactive_sid, pGroupInfo->Groups[i].Sid)) + return false; + return true; +} + +bool +fhandler_pty_master::setup_pseudoconsole () +{ + /* 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(). */ + HMODULE hModule = GetModuleHandle ("kernel32.dll"); + FARPROC func = GetProcAddress (hModule, "CreatePseudoConsole"); + HRESULT (WINAPI *CreatePseudoConsole) + (COORD, HANDLE, HANDLE, DWORD, HPCON *) = NULL; + if (!func) + return false; + CreatePseudoConsole = + (HRESULT (WINAPI *) (COORD, HANDLE, HANDLE, DWORD, HPCON *)) func; + COORD size = {80, 25}; + CreatePipe (&from_master, &to_slave, &sec_none, 0); + HRESULT res = CreatePseudoConsole (size, from_master, to_master, + 0, &get_ttyp ()->hPseudoConsole); + if (res != S_OK) + { + system_printf ("CreatePseudoConsole() failed. %08x\n", + GetLastError()); + CloseHandle (from_master); + CloseHandle (to_slave); + from_master = from_master_cyg; + to_slave = NULL; + get_ttyp ()->hPseudoConsole = NULL; + return false; + } + + /* If master process is running as service, attaching to + pseudo console should be done in fork. If attaching + is done in spawn for inetd or sshd, it fails because + the helper process is running as privileged user while + slave process is not. */ + if (is_running_as_service ()) + get_ttyp ()->attach_pcon_in_fork = true; + + SIZE_T bytesRequired; + InitializeProcThreadAttributeList (NULL, 1, 0, &bytesRequired); + STARTUPINFOEXW si_helper; + ZeroMemory (&si_helper, sizeof (si_helper)); + si_helper.StartupInfo.cb = sizeof (STARTUPINFOEXW); + si_helper.lpAttributeList = (PPROC_THREAD_ATTRIBUTE_LIST) + HeapAlloc (GetProcessHeap (), 0, bytesRequired); + InitializeProcThreadAttributeList (si_helper.lpAttributeList, + 1, 0, &bytesRequired); + UpdateProcThreadAttribute (si_helper.lpAttributeList, + 0, + PROC_THREAD_ATTRIBUTE_PSEUDOCONSOLE, + get_ttyp ()->hPseudoConsole, + sizeof (get_ttyp ()->hPseudoConsole), + NULL, NULL); + HANDLE hello = CreateEvent (&sec_none, true, false, NULL); + HANDLE goodbye = CreateEvent (&sec_none, true, false, NULL); + /* Create a pipe for receiving pseudo console handles */ + HANDLE hr, hw; + CreatePipe (&hr, &hw, &sec_none, 0); + /* Create helper process */ + WCHAR cmd[256]; + 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_helper.StartupInfo.dwFlags = STARTF_USESTDHANDLES; + si_helper.StartupInfo.hStdInput = NULL; + si_helper.StartupInfo.hStdOutput = NULL; + si_helper.StartupInfo.hStdError = NULL; + PROCESS_INFORMATION pi_helper; +#if 0 + PSECURITY_ATTRIBUTES sa = sec_user((PSECURITY_ATTRIBUTES) alloca (1024), + cygheap->user.sid (), + well_known_world_sid, + GENERIC_READ | GENERIC_WRITE); +#endif + CreateProcessW (NULL, cmd, &sec_none, &sec_none, + TRUE, EXTENDED_STARTUPINFO_PRESENT, + NULL, NULL, &si_helper.StartupInfo, &pi_helper); + WaitForSingleObject (hello, INFINITE); + /* Retrieve pseudo console handles */ + DWORD rLen; + char buf[64]; + ReadFile (hr, buf, sizeof (buf), &rLen, NULL); + buf[rLen] = '\0'; + HANDLE hpConIn, hpConOut; + sscanf (buf, "StdHandles=%p,%p", &hpConIn, &hpConOut); + DuplicateHandle (pi_helper.hProcess, hpConIn, + GetCurrentProcess (), &hpConIn, 0, + TRUE, DUPLICATE_SAME_ACCESS); + DuplicateHandle (pi_helper.hProcess, hpConOut, + GetCurrentProcess (), &hpConOut, 0, + TRUE, DUPLICATE_SAME_ACCESS); + CloseHandle (hr); + CloseHandle (hw); + /* Clean up */ + DeleteProcThreadAttributeList (si_helper.lpAttributeList); + HeapFree (GetProcessHeap (), 0, si_helper.lpAttributeList); + /* Setting information of stuffs regarding pseudo console */ + get_ttyp ()->hHelperGoodbye = goodbye; + get_ttyp ()->hHelperProcess = pi_helper.hProcess; + get_ttyp ()->HelperProcessId = pi_helper.dwProcessId; + CloseHandle (from_master); + CloseHandle (to_master); + from_master = hpConIn; + to_master = hpConOut; + return true; +} + bool fhandler_pty_master::setup () { @@ -1695,10 +2301,15 @@ fhandler_pty_master::setup () SECURITY_ATTRIBUTES sa = { sizeof (SECURITY_ATTRIBUTES), NULL, TRUE }; /* Find an unallocated pty to use. */ - int unit = cygwin_shared->tty.allocate (from_master, get_output_handle ()); + int unit = cygwin_shared->tty.allocate (from_master_cyg, get_output_handle ()); if (unit < 0) return false; + /* from_master should be used for pseudo console. + Just copy from_master_cyg here for the case that + pseudo console is not available. */ + from_master = from_master_cyg; + ProtectHandle1 (get_output_handle (), to_pty); tty& t = *cygwin_shared->tty[unit]; @@ -1715,7 +2326,7 @@ fhandler_pty_master::setup () char pipename[sizeof("ptyNNNN-to-master-cyg")]; __small_sprintf (pipename, "pty%d-to-master", unit); - res = fhandler_pipe::create (&sec_none, &get_handle (), &to_master, + res = fhandler_pipe::create (&sec_none, &from_slave, &to_master, fhandler_pty_common::pipesize, pipename, 0); if (res) { @@ -1724,7 +2335,7 @@ fhandler_pty_master::setup () } __small_sprintf (pipename, "pty%d-to-master-cyg", unit); - res = fhandler_pipe::create (&sec_none, &get_handle_cyg (), &to_master_cyg, + res = fhandler_pipe::create (&sec_none, &get_handle (), &to_master_cyg, fhandler_pty_common::pipesize, pipename, 0); if (res) { @@ -1732,7 +2343,7 @@ fhandler_pty_master::setup () goto err; } - ProtectHandle1 (get_handle (), from_pty); + ProtectHandle1 (from_slave, from_pty); __small_sprintf (pipename, "pty%d-echoloop", unit); res = fhandler_pipe::create (&sec_none, &echo_r, &echo_w, @@ -1797,7 +2408,10 @@ fhandler_pty_master::setup () goto err; } + setup_pseudoconsole (); + t.set_from_master (from_master); + t.set_from_master_cyg (from_master_cyg); t.set_to_master (to_master); t.set_to_master_cyg (to_master_cyg); t.winsize.ws_col = 80; @@ -1807,19 +2421,20 @@ fhandler_pty_master::setup () dev ().parse (DEV_PTYM_MAJOR, unit); termios_printf ("this %p, pty%d opened - from_pty <%p,%p>, to_pty %p", - this, unit, get_handle (), get_handle_cyg (), + this, unit, from_slave, get_handle (), get_output_handle ()); return true; err: __seterrno (); + close_maybe (from_slave); close_maybe (get_handle ()); - close_maybe (get_handle_cyg ()); close_maybe (get_output_handle ()); close_maybe (input_available_event); close_maybe (output_mutex); close_maybe (input_mutex); close_maybe (from_master); + close_maybe (from_master_cyg); close_maybe (to_master); close_maybe (to_master_cyg); close_maybe (echo_r); @@ -1840,14 +2455,20 @@ fhandler_pty_master::fixup_after_fork (HANDLE parent) if (myself->pid == t.master_pid) { t.set_from_master (arch->from_master); + t.set_from_master_cyg (arch->from_master_cyg); t.set_to_master (arch->to_master); t.set_to_master_cyg (arch->to_master_cyg); } arch->dwProcessId = wpid; } from_master = arch->from_master; + from_master_cyg = arch->from_master_cyg; to_master = arch->to_master; to_master_cyg = arch->to_master_cyg; +#if 0 /* Not sure if this is necessary. */ + from_slave = arch->from_slave; + to_slave = arch->to_slave; +#endif report_tty_counts (this, "inherited master", ""); } @@ -1857,7 +2478,8 @@ fhandler_pty_master::fixup_after_exec () if (!close_on_exec ()) fixup_after_fork (spawn_info->parent); else - from_master = to_master = to_master_cyg = NULL; + from_master = from_master_cyg = to_master = to_master_cyg = + from_slave = to_slave = NULL; } BOOL diff --git a/winsup/cygwin/fork.cc b/winsup/cygwin/fork.cc index 7e1c08990..4d4b000f0 100644 --- a/winsup/cygwin/fork.cc +++ b/winsup/cygwin/fork.cc @@ -134,6 +134,30 @@ child_info::prefork (bool detached) int __stdcall frok::child (volatile char * volatile here) { + cygheap_fdenum cfd (false); + while (cfd.next () >= 0) + if (cfd->get_major () == DEV_PTYM_MAJOR) + { + fhandler_base *fh = cfd; + fhandler_pty_master *ptym = (fhandler_pty_master *) fh; + if (ptym->getPseudoConsole () && + !fhandler_console::get_console_process_id ( + ptym->getHelperProcessId (), true)) + + { + debug_printf ("found a PTY master %d: helper_PID=%d", + ptym->get_minor (), ptym->getHelperProcessId ()); + if (ptym->attach_pcon_in_fork ()) + { + FreeConsole (); + if (!AttachConsole (ptym->getHelperProcessId ())) + /* Error */; + else + break; + } + } + } + HANDLE& hParent = ch.parent; sync_with_parent ("after longjmp", true); diff --git a/winsup/cygwin/select.cc b/winsup/cygwin/select.cc index 85242ec06..495754026 100644 --- a/winsup/cygwin/select.cc +++ b/winsup/cygwin/select.cc @@ -667,6 +667,9 @@ peek_pipe (select_record *s, bool from_select) fhm->flush_to_slave (); } break; + case DEV_PTYS_MAJOR: + ((fhandler_pty_slave *) fh)->reset_switch_to_pcon (); + break; default: if (fh->get_readahead_valid ()) { @@ -1176,17 +1179,32 @@ verify_tty_slave (select_record *me, fd_set *readfds, fd_set *writefds, return set_bits (me, readfds, writefds, exceptfds); } +static int +pty_slave_startup (select_record *s, select_stuff *) +{ + fhandler_base *fh = (fhandler_base *) s->fh; + ((fhandler_pty_slave *) fh)->mask_switch_to_pcon (true); + return 1; +} + +static void +pty_slave_cleanup (select_record *s, select_stuff *) +{ + fhandler_base *fh = (fhandler_base *) s->fh; + ((fhandler_pty_slave *) fh)->mask_switch_to_pcon (false); +} + select_record * fhandler_pty_slave::select_read (select_stuff *ss) { select_record *s = ss->start.next; s->h = input_available_event; - s->startup = no_startup; + s->startup = pty_slave_startup; s->peek = peek_pipe; s->verify = verify_tty_slave; s->read_selected = true; s->read_ready = false; - s->cleanup = NULL; + s->cleanup = pty_slave_cleanup; return s; } diff --git a/winsup/cygwin/spawn.cc b/winsup/cygwin/spawn.cc index 579b3c9c3..8f9bcb68e 100644 --- a/winsup/cygwin/spawn.cc +++ b/winsup/cygwin/spawn.cc @@ -288,6 +288,54 @@ child_info_spawn::worker (const char *prog_arg, const char *const *argv, return -1; } + DWORD pidRestore = + fhandler_console::get_console_process_id (GetCurrentProcessId (), false); + bool attach_to_pcon = false; + fhandler_pty_slave *ptys = NULL; + for (int fd = 0; fd < 3; fd ++) + { + fhandler_base *fh = ::cygheap->fdtab[fd]; + if (fh && fh->get_major () == DEV_PTYS_MAJOR) + { + ptys = (fhandler_pty_slave *) fh; + if (ptys->getPseudoConsole () && + !fhandler_console::get_console_process_id ( + ptys->getHelperProcessId (), true)) + { + DWORD dwHelperProcessId = ptys->getHelperProcessId (); + debug_printf ("found a PTY slave %d: helper_PID=%d", + fh->get_minor (), dwHelperProcessId); + FreeConsole (); + if (!AttachConsole (dwHelperProcessId)) + { + /* Fallback */ + DWORD target[3] = { + STD_INPUT_HANDLE, + STD_OUTPUT_HANDLE, + STD_ERROR_HANDLE + }; + if (fd == 0) + { + ptys->set_handle (ptys->get_handle_cyg ()); + SetStdHandle (target[fd], ptys->get_handle ()); + } + else + { + ptys->set_output_handle (ptys->get_output_handle_cyg ()); + SetStdHandle (target[fd], ptys->get_output_handle ()); + } + } + else + { + attach_to_pcon = true; + break; + } + } + } + } + if (ptys) + ptys->fixup_after_attach (true); + av newargv; linebuf cmd; PWCHAR envblock = NULL; @@ -867,6 +915,13 @@ child_info_spawn::worker (const char *prog_arg, const char *const *argv, this->cleanup (); if (envblock) free (envblock); + + if (attach_to_pcon && pidRestore) + { + FreeConsole (); + AttachConsole (pidRestore); + } + return (int) res; } diff --git a/winsup/cygwin/strace.cc b/winsup/cygwin/strace.cc index 35f8a59ae..b1eb5f3e4 100644 --- a/winsup/cygwin/strace.cc +++ b/winsup/cygwin/strace.cc @@ -279,6 +279,30 @@ strace::vprntf (unsigned category, const char *func, const char *fmt, va_list ap CloseHandle (h); } } +#if 1 /* Experimental code */ + /* PTY with pseudo console cannot display data written to + STD_ERROR_HANDLE (output_handle) if the process is cygwin + process. output_handle works only in native console apps. + Therefore the data should be written to output_handle_cyg + as well. */ + fhandler_base *fh = ::cygheap->fdtab[2]; + if (fh && fh->get_major () == DEV_PTYS_MAJOR) + { + fhandler_pty_slave *ptys = (fhandler_pty_slave *) fh; + if (ptys->getPseudoConsole ()) + { + HANDLE h_cyg = ptys->get_output_handle_cyg (); + if (buf[len-1] == '\n' && len < NT_MAX_PATH - 1) + { + buf[len-1] = '\r'; + buf[len] = '\n'; + len ++; + } + WriteFile (h_cyg, buf, len, &done, 0); + FlushFileBuffers (h_cyg); + } + } +#endif } #ifndef NOSTRACE diff --git a/winsup/cygwin/tty.cc b/winsup/cygwin/tty.cc index ad46cb312..64bbf2542 100644 --- a/winsup/cygwin/tty.cc +++ b/winsup/cygwin/tty.cc @@ -234,7 +234,14 @@ tty::init () was_opened = false; master_pid = 0; is_console = false; + attach_pcon_in_fork = false; + hPseudoConsole = NULL; column = 0; + switch_to_pcon = false; + screen_alternated = false; + mask_switch_to_pcon = false; + pcon_pid = 0; + num_pcon_attached_slaves = 0; } HANDLE diff --git a/winsup/cygwin/tty.h b/winsup/cygwin/tty.h index 9aee43b9c..fd8bee569 100644 --- a/winsup/cygwin/tty.h +++ b/winsup/cygwin/tty.h @@ -28,6 +28,8 @@ details. */ #define MIN_CTRL_C_SLOP 50 #endif +typedef void *HPCON; + #include class tty_min { @@ -88,14 +90,27 @@ public: private: HANDLE _from_master; + HANDLE _from_master_cyg; HANDLE _to_master; HANDLE _to_master_cyg; + HPCON hPseudoConsole; + HANDLE hHelperProcess; + DWORD HelperProcessId; + HANDLE hHelperGoodbye; + bool attach_pcon_in_fork; + bool switch_to_pcon; + bool screen_alternated; + bool mask_switch_to_pcon; + pid_t pcon_pid; + int num_pcon_attached_slaves; public: - HANDLE from_master() const { return _from_master; } - HANDLE to_master() const { return _to_master; } - HANDLE to_master_cyg() const { return _to_master_cyg; } + HANDLE from_master () const { return _from_master; } + HANDLE from_master_cyg () const { return _from_master_cyg; } + HANDLE to_master () const { return _to_master; } + HANDLE to_master_cyg () const { return _to_master_cyg; } void set_from_master (HANDLE h) { _from_master = h; } + void set_from_master_cyg (HANDLE h) { _from_master_cyg = h; } void set_to_master (HANDLE h) { _to_master = h; } void set_to_master_cyg (HANDLE h) { _to_master_cyg = h; } @@ -117,7 +132,9 @@ public: void set_master_ctl_closed () {master_pid = -1;} static void __stdcall create_master (int); static void __stdcall init_session (); + friend class fhandler_pty_common; friend class fhandler_pty_master; + friend class fhandler_pty_slave; }; class tty_list diff --git a/winsup/utils/cygwin-console-helper.cc b/winsup/utils/cygwin-console-helper.cc index 8f62ed7e6..bef143f96 100644 --- a/winsup/utils/cygwin-console-helper.cc +++ b/winsup/utils/cygwin-console-helper.cc @@ -1,12 +1,24 @@ #include +#include int main (int argc, char **argv) { char *end; - if (argc != 3) + if (argc < 3) exit (1); HANDLE h = (HANDLE) strtoul (argv[1], &end, 0); SetEvent (h); + if (argc == 4) /* Pseudo console helper mode for PTY */ + { + HANDLE hPipe = (HANDLE) strtoul (argv[3], &end, 0); + char buf[64]; + sprintf (buf, "StdHandles=%p,%p\n", + GetStdHandle (STD_INPUT_HANDLE), + GetStdHandle (STD_OUTPUT_HANDLE)); + DWORD dwLen; + WriteFile (hPipe, buf, strlen (buf), &dwLen, NULL); + CloseHandle (hPipe); + } h = (HANDLE) strtoul (argv[2], &end, 0); WaitForSingleObject (h, INFINITE); exit (0); -- 2.17.0