From f7178cc0b5709a134ca5e4e1d85dfb53dbe82f81 Mon Sep 17 00:00:00 2001 From: Takashi Yano Date: Sun, 30 Aug 2020 16:55:10 +0900 Subject: [PATCH v8] 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 in terminfo database. Also, 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 | 133 ++++++++++++++++++++++++++++++++++ winsup/cygwin/spawn.cc | 19 +++-- winsup/cygwin/tty.cc | 1 + winsup/cygwin/tty.h | 1 + 5 files changed, 148 insertions(+), 7 deletions(-) diff --git a/winsup/cygwin/fhandler.h b/winsup/cygwin/fhandler.h index 9fd95c098..f55bcf9d1 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, bool bg); 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..2a7e66afd 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,120 @@ fhandler_pty_slave::close_pseudoconsole (void) get_ttyp ()->pcon_start = false; } } + +bool +fhandler_pty_slave::term_has_pcon_cap (const WCHAR *env, bool background) +{ + 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; + + /* Check if terminal has capability which pusedo console needs */ + 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 user7 = 294; /* u7 (query cursor position) entry index */ + if (user7 >= 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 + user7 >= (int16_t *) (terminfo + n)) + return false; + if (str_idx[user7] == -1) + return false; + const char *user7_str = str_table + str_idx[user7]; + if (user7_str >= str_table + str_size) + return false; + if (user7_str >= terminfo + n) + return false; + if (strcmp (user7_str, "\033[6n") != 0) + return false; + + /* If the process is background, or another process is already + started under pseudo console, responce to CSI6n may be eaten + by the other process. Therefore, checking set-title capability + should be skipped. */ + if (get_ttyp ()->pcon_pid && get_ttyp ()->pcon_pid != myself->pid + && !!pinfo (get_ttyp ()->pcon_pid)) + return true; + if (background) + return true; + + /* Check if terminal has set-title capability */ + tcflag_t c_lflag = get_ttyp ()->ti.c_lflag; + get_ttyp ()->ti.c_lflag &= ~ICANON; + write ("\033[6n\033]0;\033\\\033[6n", 14); + char buf[1024]; + char *p = buf; + int len = sizeof (buf) - 1; + int x1, y1, x2, y2; + do + { + size_t n = len; + read (p, n); + p += n; + len -= n; + *p = '\0'; + char *p2 = strrchr (buf, '\033'); + if (p2 == NULL || sscanf (p2, "\033[%d;%dR", &y2, &x2) != 2) + continue; + *p2 = '\0'; + char *p1 = strrchr (buf, '\033'); + *p2 = '\033'; + if (p1 == NULL || sscanf (p1, "\033[%d;%dR", &y1, &x1) != 2) + continue; + break; + } + while (len); + 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 + { + for (int i=0; ihas_set_title = false; + } + return true; +} diff --git a/winsup/cygwin/spawn.cc b/winsup/cygwin/spawn.cc index a2f7697d7..9a5e3f6ef 100644 --- a/winsup/cygwin/spawn.cc +++ b/winsup/cygwin/spawn.cc @@ -647,13 +647,18 @@ 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; + bool background = ctty_pgid && ctty_pgid != myself->pgid; + if (!ptys_primary->term_has_pcon_cap (envblock, background)) + 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..e6d57ff6e 100644 --- a/winsup/cygwin/tty.cc +++ b/winsup/cygwin/tty.cc @@ -242,6 +242,7 @@ tty::init () term_code_page = 0; pcon_last_time = 0; pcon_start = false; + has_set_title = false; } HANDLE diff --git a/winsup/cygwin/tty.h b/winsup/cygwin/tty.h index c491d3891..13af95687 100644 --- a/winsup/cygwin/tty.h +++ b/winsup/cygwin/tty.h @@ -101,6 +101,7 @@ private: UINT term_code_page; DWORD pcon_last_time; HANDLE h_pcon_write_pipe; + bool has_set_title; public: HANDLE from_master () const { return _from_master; } -- 2.28.0