From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: Received: from conssluserg-01.nifty.com (conssluserg-01.nifty.com [210.131.2.80]) by sourceware.org (Postfix) with ESMTPS id 0EA733851C0D for ; Wed, 13 May 2020 12:16:24 +0000 (GMT) DMARC-Filter: OpenDMARC Filter v1.3.2 sourceware.org 0EA733851C0D Received: from Express5800-S70 (v040007.dynamic.ppp.asahi-net.or.jp [124.155.40.7]) (authenticated) by conssluserg-01.nifty.com with ESMTP id 04DCG4kx026351 for ; Wed, 13 May 2020 21:16:04 +0900 DKIM-Filter: OpenDKIM Filter v2.10.3 conssluserg-01.nifty.com 04DCG4kx026351 X-Nifty-SrcIP: [124.155.40.7] Date: Wed, 13 May 2020 21:16:09 +0900 From: Takashi Yano To: cygwin-developers@cygwin.com Subject: New implementation of pseudo console support (experimental) Message-Id: <20200513211609.011d188c3a735b00d55591df@nifty.ne.jp> X-Mailer: Sylpheed 3.7.0 (GTK+ 2.24.30; i686-pc-mingw32) Mime-Version: 1.0 Content-Type: multipart/mixed; boundary="Multipart=_Wed__13_May_2020_21_16_09_+0900_HOndtKF3dzJk/97z" X-Spam-Status: No, score=-9.7 required=5.0 tests=BAYES_00, DKIM_SIGNED, DKIM_VALID, DKIM_VALID_AU, DKIM_VALID_EF, GIT_PATCH_0, RCVD_IN_DNSWL_NONE, SPF_HELO_NONE, SPF_PASS, TXREP autolearn=ham autolearn_force=no version=3.4.2 X-Spam-Checker-Version: SpamAssassin 3.4.2 (2018-09-13) on server2.sourceware.org X-BeenThere: cygwin-developers@cygwin.com X-Mailman-Version: 2.1.29 Precedence: list List-Id: Cygwin core component developers mailing list List-Unsubscribe: , List-Archive: List-Help: List-Subscribe: , X-List-Received-Date: Wed, 13 May 2020 12:16:29 -0000 This is a multi-part message in MIME format. --Multipart=_Wed__13_May_2020_21_16_09_+0900_HOndtKF3dzJk/97z Content-Type: text/plain; charset=US-ASCII Content-Transfer-Encoding: 7bit Hello everyone. This time, I have experimentally implemented a new pseudo console support. In this implementation, pseudo console is created for each native console app. The advantage and disadvantage of this implementation are as follows. Advantage: 1) No performance degradation in pty output for cygwin process. https://sourceware.org/pipermail/cygwin/2020-February/243651.html 2) Free from the problem caused by difference of behaviour of control sequences between real terminal and pseudo console. https://sourceware.org/pipermail/cygwin/2019-December/243074.html https://sourceware.org/pipermail/cygwin/2020-February/243648.html 3) Free from the problem in cgdb and emacs gud. https://sourceware.org/pipermail/cygwin/2020-January/243394.html https://sourceware.org/pipermail/cygwin/2020-March/243939.html 4) Redrawing screen on executing native console apps is not necessary. 5) cygwin-console-helper is not necessary for the pseudo console support. Disadvantage: 1) Pseudo console is disabled if one of stdin, stdout or stderr is redirected. 2) Pseudo console is disabled if the native console app is executed in background. 3) The cygwin program which call console API directly does not work. 4) The apps which use console API cannot be debugged with gdb. 5) Type ahead key inputs are discarded while native console app is executed. 6) cmd.exe hangs up on typing some keys if cmd.exe is executed in background. (same as cygwin 3.0.7) 7) Code page cannot be changed by chcp.com. The patch attached is for the current git head. Could you please test? Any comments and suggestions will be appreciated. -- Takashi Yano --Multipart=_Wed__13_May_2020_21_16_09_+0900_HOndtKF3dzJk/97z Content-Type: text/plain; name="pcon2.diff" Content-Disposition: attachment; filename="pcon2.diff" Content-Transfer-Encoding: 7bit diff --git a/winsup/cygwin/fhandler.h b/winsup/cygwin/fhandler.h index ae64086df..6da92ce60 100644 --- a/winsup/cygwin/fhandler.h +++ b/winsup/cygwin/fhandler.h @@ -2264,6 +2264,7 @@ class fhandler_pty_common: public fhandler_termios return get_ttyp ()->h_pseudo_console; } bool to_be_read_from_pcon (void); + void resize_pseudo_console2 (struct winsize *); protected: BOOL process_opost_output (HANDLE h, @@ -2355,6 +2356,9 @@ class fhandler_pty_slave: public fhandler_pty_common void wait_pcon_fwd (void); void pull_pcon_input (void); void update_pcon_input_state (bool need_lock); + bool setup_pseudoconsole2 (STARTUPINFOEXW *si); + void close_pseudoconsole2 (void); + void wait_pcon_fwd2 (void); }; #define __ptsname(buf, unit) __small_sprintf ((buf), "/dev/pty%d", (unit)) diff --git a/winsup/cygwin/fhandler_tty.cc b/winsup/cygwin/fhandler_tty.cc index 8547ec7c4..11b84d332 100644 --- a/winsup/cygwin/fhandler_tty.cc +++ b/winsup/cygwin/fhandler_tty.cc @@ -41,6 +41,8 @@ extern "C" { VOID WINAPI ClosePseudoConsole (HPCON); } +#define USE_PCON_MODE2 true + #define close_maybe(h) \ do { \ if (h && h != INVALID_HANDLE_VALUE) \ @@ -1295,6 +1297,7 @@ fhandler_pty_slave::reset_switch_to_pcon (void) get_ttyp ()->switch_to_pcon_in = false; get_ttyp ()->switch_to_pcon_out = false; init_console_handler (true); + get_ttyp ()->h_pseudo_console2 = NULL; } void @@ -1554,8 +1557,8 @@ fhandler_pty_slave::mask_switch_to_pcon_in (bool mask) bool fhandler_pty_common::to_be_read_from_pcon (void) { - return !get_ttyp ()->pcon_in_empty || - (get_ttyp ()->switch_to_pcon_in && !get_ttyp ()->mask_switch_to_pcon_in); + return !get_ttyp ()->pcon_in_empty || get_ttyp ()->h_pseudo_console2 + || (get_ttyp ()->switch_to_pcon_in && !get_ttyp ()->mask_switch_to_pcon_in); } void __reg3 @@ -2059,6 +2062,8 @@ fhandler_pty_slave::ioctl (unsigned int cmd, void *arg) cleanup: restore_reattach_pcon (); } + if (get_ttyp ()->h_pseudo_console2 && get_ttyp ()->pcon_pid) + resize_pseudo_console2 ((struct winsize *) arg); if (get_ttyp ()->winsize.ws_row != ((struct winsize *) arg)->ws_row || get_ttyp ()->winsize.ws_col != ((struct winsize *) arg)->ws_col) @@ -2348,6 +2353,27 @@ fhandler_pty_common::close () return 0; } +void +fhandler_pty_common::resize_pseudo_console2 (struct winsize *ws) +{ + COORD size; + size.X = ws->ws_col; + size.Y = ws->ws_row; + pinfo p (get_ttyp ()->pcon_pid); + if (p) + { + HPCON_INTERNAL hpcon_local; + HANDLE pcon_owner = + OpenProcess (PROCESS_DUP_HANDLE, FALSE, p->exec_dwProcessId); + DuplicateHandle (pcon_owner, get_ttyp ()->h_pcon_write_pipe, + GetCurrentProcess (), &hpcon_local.hWritePipe, + 0, TRUE, DUPLICATE_SAME_ACCESS); + ResizePseudoConsole ((HPCON) &hpcon_local, size); + CloseHandle (pcon_owner); + CloseHandle (hpcon_local.hWritePipe); + } +} + void fhandler_pty_master::cleanup () { @@ -2497,11 +2523,57 @@ fhandler_pty_master::write (const void *ptr, size_t len) get_ttyp ()->req_flush_pcon_input = true; DWORD wLen; - WriteFile (to_slave, buf, nlen, &wLen, NULL); + HANDLE write_to = + get_ttyp ()->h_pseudo_console2 ? get_output_handle () : to_slave; + + if (get_ttyp ()->pcon2_start) + { + /* Pseudo condole support mode-2 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 = 64; + static char wpbuf[wpbuf_len]; + static int ixput = 0; + + if (ixput == 0 && buf[0] != '\033') + { /* fail-safe */ + WriteFile (write_to, "\033[1;1R", 6, &wLen, NULL); /* dummy */ + get_ttyp ()->pcon2_start = false; + } + else + { + if (ixput + nlen < wpbuf_len) + for (size_t i=0; ipcon2_start = false; + WriteFile (write_to, buf, nlen, &wLen, NULL); + } + if (ixput && memchr (wpbuf, 'R', ixput)) + { + WriteFile (write_to, wpbuf, ixput, &wLen, NULL); + ixput = 0; + get_ttyp ()->pcon2_start = false; + } + ReleaseMutex (input_mutex); + mb_str_free (buf); + return len; + } + } + /* Workaround for rlwrap. Replace NL to CR. */ + char *p = (char *) memchr (buf, '\n', nlen); + if (p) *p = '\r'; + + WriteFile (write_to, buf, nlen, &wLen, NULL); get_ttyp ()->pcon_in_empty = false; ReleaseMutex (input_mutex); +#if !USE_PCON_MODE2 /* Use line_edit () in order to set input_available_event. */ bool is_echo = tc ()->ti.c_lflag & ECHO; if (!get_ttyp ()->mask_switch_to_pcon_in) @@ -2527,6 +2599,7 @@ fhandler_pty_master::write (const void *ptr, size_t len) eat_readahead (-1); SetEvent (input_available_event); } +#endif /* USE_PCON_MODE2 */ mb_str_free (buf); return len; @@ -2608,6 +2681,8 @@ fhandler_pty_master::ioctl (unsigned int cmd, void *arg) size.Y = ((struct winsize *) arg)->ws_row; ResizePseudoConsole (get_pseudo_console (), size); } + if (get_ttyp ()->h_pseudo_console2 && get_ttyp ()->pcon_pid) + resize_pseudo_console2 ((struct winsize *) arg); if (get_ttyp ()->winsize.ws_row != ((struct winsize *) arg)->ws_row || get_ttyp ()->winsize.ws_col != ((struct winsize *) arg)->ws_col) { @@ -2884,6 +2959,19 @@ fhandler_pty_slave::wait_pcon_fwd (void) cygwait (get_ttyp ()->fwd_done, INFINITE); } +void +fhandler_pty_slave::wait_pcon_fwd2 (void) +{ + const int sleep_in_pcon = 16; + const int time_to_wait = sleep_in_pcon * 2 + 1/* margine */; + get_ttyp ()->pcon_last_time = GetTickCount (); + while (GetTickCount () - get_ttyp ()->pcon_last_time < time_to_wait) + { + int tw = time_to_wait - (GetTickCount () - get_ttyp ()->pcon_last_time); + cygwait (tw); + } +} + void fhandler_pty_slave::trigger_redraw_screen (void) { @@ -3267,6 +3355,8 @@ fhandler_pty_master::pty_master_fwd_thread () Sleep (1); } } + if (USE_PCON_MODE2) + get_ttyp ()->pcon_last_time = GetTickCount (); if (!ReadFile (from_slave, outbuf, sizeof outbuf, &rlen, NULL)) { termios_printf ("ReadFile for forwarding failed, %E"); @@ -3274,8 +3364,9 @@ fhandler_pty_master::pty_master_fwd_thread () } ssize_t wlen = rlen; char *ptr = outbuf; - if (get_pseudo_console ()) + if (get_pseudo_console () || get_ttyp ()->h_pseudo_console2) { +#if !USE_PCON_MODE2 /* Avoid duplicating slave output which is already sent to to_master_cyg */ if (!get_ttyp ()->switch_to_pcon_out) @@ -3328,6 +3419,7 @@ fhandler_pty_master::pty_master_fwd_thread () rlen -= 4; } wlen = rlen; +#endif /* USE_PCON_MODE2 */ size_t nlen; char *buf = convert_mb_str @@ -3697,7 +3789,8 @@ fhandler_pty_master::setup () t.winsize.ws_col = 80; t.winsize.ws_row = 25; - setup_pseudoconsole (); + if (!USE_PCON_MODE2) + setup_pseudoconsole (); t.set_from_master (from_master); t.set_from_master_cyg (from_master_cyg); @@ -3859,3 +3952,100 @@ fhandler_pty_common::process_opost_output (HANDLE h, const void *ptr, ssize_t& l len -= towrite; return res; } + +bool +fhandler_pty_slave::setup_pseudoconsole2 (STARTUPINFOEXW *si) +{ + if (!USE_PCON_MODE2) + return false; + if (disable_pcon) + return false; + /* 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; + } + + COORD size = { + (SHORT) get_ttyp ()->winsize.ws_col, + (SHORT) get_ttyp ()->winsize.ws_row + }; + SetLastError (ERROR_SUCCESS); + HRESULT res = CreatePseudoConsole (size, get_handle (), get_output_handle (), + 1, &get_ttyp ()->h_pseudo_console2); + 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, 1, 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, + 1, 0, &bytesRequired)) + goto cleanup_heap; + if (!UpdateProcThreadAttribute (si->lpAttributeList, + 0, + PROC_THREAD_ATTRIBUTE_PSEUDOCONSOLE, + get_ttyp ()->h_pseudo_console2, + sizeof (get_ttyp ()->h_pseudo_console2), + NULL, NULL)) + goto cleanup_heap; + si->StartupInfo.dwFlags = STARTF_USESTDHANDLES; + si->StartupInfo.hStdInput = NULL; + si->StartupInfo.hStdOutput = NULL; + si->StartupInfo.hStdError = NULL; + + if (get_ttyp ()->pcon_pid == 0 || + !pinfo (get_ttyp ()->pcon_pid)) + get_ttyp ()->pcon_pid = myself->pid; + if (get_ttyp ()->h_pseudo_console2 && get_ttyp ()->pcon_pid == myself->pid) + { + HPCON_INTERNAL *hp = (HPCON_INTERNAL *) get_ttyp ()->h_pseudo_console2; + get_ttyp ()->h_pcon_write_pipe = hp->hWritePipe; + } + get_ttyp ()->pcon2_start = true; + return true; + +cleanup_heap: + HeapFree (GetProcessHeap (), 0, si->lpAttributeList); +cleanup_pseudo_console: + if (get_ttyp ()->h_pseudo_console2) + { + HPCON_INTERNAL *hp = (HPCON_INTERNAL *) get_ttyp ()->h_pseudo_console2; + HANDLE tmp = hp->hConHostProcess; + ClosePseudoConsole (get_ttyp ()->h_pseudo_console2); + CloseHandle (tmp); + } +fallback: + get_ttyp ()->h_pseudo_console2 = NULL; + return false; +} + +void +fhandler_pty_slave::close_pseudoconsole2 (void) +{ + if (get_ttyp ()->h_pseudo_console2) + { + HPCON_INTERNAL *hp = (HPCON_INTERNAL *) get_ttyp ()->h_pseudo_console2; + HANDLE tmp = hp->hConHostProcess; + ClosePseudoConsole (get_ttyp ()->h_pseudo_console2); + CloseHandle (tmp); + get_ttyp ()->h_pseudo_console2 = NULL; + } +} diff --git a/winsup/cygwin/spawn.cc b/winsup/cygwin/spawn.cc index 3e8c8367a..d9ed8cfc4 100644 --- a/winsup/cygwin/spawn.cc +++ b/winsup/cygwin/spawn.cc @@ -194,6 +194,24 @@ handle (int fd, bool writing) return h; } +static bool +is_console_app (WCHAR *filename) +{ + HANDLE h; + const int id_offset = 92; + h = CreateFileW (filename, GENERIC_READ, FILE_SHARE_READ, + NULL, OPEN_EXISTING, 0, NULL); + char buf[1024]; + DWORD n; + ReadFile (h, buf, sizeof (buf), &n, 0); + CloseHandle (h); + char *p = (char *) memmem (buf, n, "PE\0\0", 4); + if (p && p + id_offset <= buf + n) + return p[id_offset] == '\003'; /* 02: GUI, 03: console */ + else + return false; +} + int iscmd (const char *argv0, const char *what) { @@ -583,6 +601,9 @@ child_info_spawn::worker (const char *prog_arg, const char *const *argv, /* Attach to pseudo console if pty salve is used */ pid_restore = fhandler_console::get_console_process_id (GetCurrentProcessId (), false); + int minor = -1; + int ptys_cnt = 0; + fhandler_pty_slave *ptys = NULL; for (int i = 0; i < 3; i ++) { const int chk_order[] = {1, 0, 2}; @@ -590,7 +611,11 @@ child_info_spawn::worker (const char *prog_arg, const char *const *argv, fhandler_base *fh = ::cygheap->fdtab[fd]; if (fh && fh->get_major () == DEV_PTYS_MAJOR) { - fhandler_pty_slave *ptys = (fhandler_pty_slave *) fh; + ptys = (fhandler_pty_slave *) fh; + if (minor < 0) + minor = fh->get_minor (); + if (minor == fh->get_minor ()) + ptys_cnt++; if (ptys->get_pseudo_console ()) { DWORD helper_process_id = ptys->get_helper_process_id (); @@ -632,6 +657,20 @@ child_info_spawn::worker (const char *prog_arg, const char *const *argv, if (!iscygwin ()) init_console_handler (myself->ctty > 0); + bool use_pcon_mode2 = false; + STARTUPINFOEXW si_pcon; + ZeroMemory (&si_pcon, sizeof (si_pcon)); + STARTUPINFOW *si_tmp = &si; + if (!iscygwin () && ptys_cnt == 3 && ptys + && ctty_pgid && ctty_pgid == myself->pgid + && is_console_app (runpath)) + if (ptys->setup_pseudoconsole2 (&si_pcon)) + { + c_flags |= EXTENDED_STARTUPINFO_PRESENT; + si_tmp = &si_pcon.StartupInfo; + use_pcon_mode2 = true; + } + loop: /* When ruid != euid we create the new process under the current original account and impersonate in child, this way maintaining the different @@ -660,7 +699,7 @@ child_info_spawn::worker (const char *prog_arg, const char *const *argv, c_flags, envblock, /* environment */ NULL, - &si, + si_tmp, &pi); } else @@ -714,7 +753,7 @@ child_info_spawn::worker (const char *prog_arg, const char *const *argv, c_flags, envblock, /* environment */ NULL, - &si, + si_tmp, &pi); if (hwst) { @@ -727,6 +766,11 @@ child_info_spawn::worker (const char *prog_arg, const char *const *argv, CloseDesktop (hdsk); } } + if (use_pcon_mode2) + { + DeleteProcThreadAttributeList (si_pcon.lpAttributeList); + HeapFree (GetProcessHeap (), 0, si_pcon.lpAttributeList); + } if (mode != _P_OVERLAY) SetHandleInformation (my_wr_proc_pipe, HANDLE_FLAG_INHERIT, @@ -897,6 +941,12 @@ child_info_spawn::worker (const char *prog_arg, const char *const *argv, && WaitForSingleObject (pi.hProcess, 0) == WAIT_TIMEOUT) wait_for_myself (); } + if (use_pcon_mode2) + { + WaitForSingleObject (pi.hProcess, INFINITE); + ptys->wait_pcon_fwd2 (); + ptys->close_pseudoconsole2 (); + } myself.exit (EXITCODE_NOSET); break; case _P_WAIT: diff --git a/winsup/cygwin/tty.cc b/winsup/cygwin/tty.cc index 0663dc5ee..9bc1457d1 100644 --- a/winsup/cygwin/tty.cc +++ b/winsup/cygwin/tty.cc @@ -250,6 +250,8 @@ tty::init () pcon_in_empty = true; req_transfer_input_to_pcon = false; req_flush_pcon_input = false; + h_pseudo_console2 = NULL; + pcon2_start = false; } HANDLE diff --git a/winsup/cygwin/tty.h b/winsup/cygwin/tty.h index a24afad06..a7546715e 100644 --- a/winsup/cygwin/tty.h +++ b/winsup/cygwin/tty.h @@ -111,6 +111,9 @@ private: bool pcon_in_empty; bool req_transfer_input_to_pcon; bool req_flush_pcon_input; + HPCON h_pseudo_console2; + HANDLE h_pcon_write_pipe; + bool pcon2_start; public: HANDLE from_master () const { return _from_master; } --Multipart=_Wed__13_May_2020_21_16_09_+0900_HOndtKF3dzJk/97z--