From a59325a9abfdbcf2c7faca898519ffc1e693a626 Mon Sep 17 00:00:00 2001 From: Takashi Yano Date: Mon, 31 Aug 2020 11:23:58 +0900 Subject: [PATCH v9] Cygwin: pty: Disable pseudo console if TERM does not have CSI6n. - Pseudo console internally sends escape sequence CSI6n (query cursor position) on startup of non-cygwin apps. If the terminal does not support CSI6n, CreateProcess() hangs waiting for response. To prevent hang, this patch disables pseudo console if the terminal does not have CSI6n. This is checked on the first execution of non-cygwin app using the following steps. 1) Check if the terminal support ANSI escape sequences by looking into terminfo database. If terminfo has cursor_home (ESC [H), the terminal is supposed to support ANSI escape sequences. 2) If the terminal supports ANSI escape sequneces, send CSI6n for a test and wait for a responce for 40ms. 3) If there is a responce within 40ms, CSI6n is supposed to be supported. Also set-title capability is checked, and removes escape sequence for setting window title if the terminal does not have the set- title capability. --- winsup/cygwin/fhandler.h | 1 + winsup/cygwin/fhandler_tty.cc | 185 ++++++++++++++++++++++++++++++++++ winsup/cygwin/spawn.cc | 18 ++-- winsup/cygwin/tty.cc | 3 + winsup/cygwin/tty.h | 3 + 5 files changed, 203 insertions(+), 7 deletions(-) diff --git a/winsup/cygwin/fhandler.h b/winsup/cygwin/fhandler.h index 9fd95c098..b4ba9428a 100644 --- a/winsup/cygwin/fhandler.h +++ b/winsup/cygwin/fhandler.h @@ -2332,6 +2332,7 @@ class fhandler_pty_slave: public fhandler_pty_common } bool setup_pseudoconsole (STARTUPINFOEXW *si, bool nopcon); void close_pseudoconsole (void); + bool term_has_pcon_cap (const WCHAR *env); void set_switch_to_pcon (void); void reset_switch_to_pcon (void); void mask_switch_to_pcon_in (bool mask); diff --git a/winsup/cygwin/fhandler_tty.cc b/winsup/cygwin/fhandler_tty.cc index 0865c1fac..12c965ea9 100644 --- a/winsup/cygwin/fhandler_tty.cc +++ b/winsup/cygwin/fhandler_tty.cc @@ -2169,6 +2169,22 @@ fhandler_pty_master::pty_master_fwd_thread () char *ptr = outbuf; if (get_ttyp ()->h_pseudo_console) { + if (!get_ttyp ()->has_set_title) + { + /* Remove Set title sequence */ + char *p0, *p1; + p0 = outbuf; + while ((p0 = (char *) memmem (p0, rlen, "\033]0;", 4))) + { + p1 = (char *) memchr (p0, '\007', rlen - (p0 - outbuf)); + if (p1) + { + memmove (p0, p1 + 1, rlen - (p1 + 1 - outbuf)); + rlen -= p1 + 1 - p0; + wlen = rlen; + } + } + } /* Remove CSI > Pm m */ int state = 0; int start_at = 0; @@ -2659,3 +2675,172 @@ fhandler_pty_slave::close_pseudoconsole (void) get_ttyp ()->pcon_start = false; } } + +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 x1, y1, x2, y2; + tcflag_t c_lflag; + DWORD t0; + + /* Check if terminal has ANSI escape sequence. */ + if (!has_ansi_escape_sequences (env)) + goto maybe_dumb; + + /* Check if terminal has CSI6n */ + c_lflag = get_ttyp ()->ti.c_lflag; + get_ttyp ()->ti.c_lflag &= ~ICANON; + get_ttyp ()->h_pseudo_console = (HPCON *)-1; /* dummy */ + get_ttyp ()->pcon_start = true; + WriteFile (get_output_handle_cyg (), "\033[6n", 4, &n, NULL); + p = buf; + len = sizeof (buf) - 1; + t0 = GetTickCount (); + do + { + if (::bytes_available (n, get_handle ()) && n) + { + ReadFile (get_handle (), p, len, &n, NULL); + p += n; + len -= n; + *p = '\0'; + char *p1 = strrchr (buf, '\033'); + if (p1 == NULL || sscanf (p1, "\033[%d;%dR", &y1, &x1) != 2) + continue; + break; + } + else if (GetTickCount () - t0 > 40) /* Timeout */ + goto not_has_csi6n; + else + Sleep (1); + } + while (len); + if (len == 0) + goto not_has_csi6n; + + get_ttyp ()->pcon_cap_checked = true; + get_ttyp ()->has_csi6n = true; + + /* Check if terminal has set-title capability */ + get_ttyp ()->pcon_start = true; + WriteFile (get_output_handle_cyg (), "\033]0;\033\\\033[6n", 10, &n, NULL); + p = buf; + len = sizeof (buf) - 1; + do + { + ReadFile (get_handle (), p, len, &n, NULL); + p += n; + len -= n; + *p = '\0'; + char *p2 = strrchr (buf, '\033'); + if (p2 == NULL || sscanf (p2, "\033[%d;%dR", &y2, &x2) != 2) + continue; + break; + } + while (len); + get_ttyp ()->pcon_start = false; + get_ttyp ()->h_pseudo_console = NULL; + get_ttyp ()->ti.c_lflag = c_lflag; + + if (len == 0) + return true; + + if (x2 == x1 && y2 == y1) + /* If "\033]0;\033\\" does not move cursor position, + set-title is supposed to be supported. */ + get_ttyp ()->has_set_title = true; + else + { + /* Try to erase garbage string caused by "\033]0;\033\\" */ + for (int i=0; ihas_set_title = false; + } + return true; + +not_has_csi6n: + get_ttyp ()->pcon_start = false; + get_ttyp ()->h_pseudo_console = NULL; + get_ttyp ()->ti.c_lflag = c_lflag; +maybe_dumb: + get_ttyp ()->has_csi6n = false; + get_ttyp ()->has_set_title = false; + get_ttyp ()->pcon_cap_checked = true; + return false; +} diff --git a/winsup/cygwin/spawn.cc b/winsup/cygwin/spawn.cc index a2f7697d7..92d190d1a 100644 --- a/winsup/cygwin/spawn.cc +++ b/winsup/cygwin/spawn.cc @@ -647,13 +647,17 @@ child_info_spawn::worker (const char *prog_arg, const char *const *argv, ZeroMemory (&si_pcon, sizeof (si_pcon)); STARTUPINFOW *si_tmp = &si; if (!iscygwin () && ptys_primary && is_console_app (runpath)) - if (ptys_primary->setup_pseudoconsole (&si_pcon, - mode != _P_OVERLAY && mode != _P_WAIT)) - { - c_flags |= EXTENDED_STARTUPINFO_PRESENT; - si_tmp = &si_pcon.StartupInfo; - enable_pcon = true; - } + { + bool nopcon = mode != _P_OVERLAY && mode != _P_WAIT; + if (!ptys_primary->term_has_pcon_cap (envblock)) + nopcon = true; + if (ptys_primary->setup_pseudoconsole (&si_pcon, nopcon)) + { + c_flags |= EXTENDED_STARTUPINFO_PRESENT; + si_tmp = &si_pcon.StartupInfo; + enable_pcon = true; + } + } loop: /* When ruid != euid we create the new process under the current original diff --git a/winsup/cygwin/tty.cc b/winsup/cygwin/tty.cc index d60f27545..7e3b88b0b 100644 --- a/winsup/cygwin/tty.cc +++ b/winsup/cygwin/tty.cc @@ -242,6 +242,9 @@ tty::init () term_code_page = 0; pcon_last_time = 0; pcon_start = false; + pcon_cap_checked = false; + has_csi6n = false; + has_set_title = false; } HANDLE diff --git a/winsup/cygwin/tty.h b/winsup/cygwin/tty.h index c491d3891..4e9199dba 100644 --- a/winsup/cygwin/tty.h +++ b/winsup/cygwin/tty.h @@ -101,6 +101,9 @@ private: UINT term_code_page; DWORD pcon_last_time; HANDLE h_pcon_write_pipe; + bool pcon_cap_checked; + bool has_csi6n; + bool has_set_title; public: HANDLE from_master () const { return _from_master; } -- 2.28.0