newlib/winsup/cygwin/tty.cc
Takashi Yano bb42852062 Cygwin: pty: Implement new pseudo console support.
- In this implementation, pseudo console is created for each native
  console app. Advantages and disadvantages of this implementation
  over the previous implementation are as follows.

  Advantages:
  1) No performance degradation in pty output for cygwin process.
      https://cygwin.com/pipermail/cygwin/2020-February/243858.html
  2) Free from the problem caused by difference of behaviour of control
     sequences between real terminal and pseudo console.
      https://cygwin.com/pipermail/cygwin/2019-December/243281.html
      https://cygwin.com/pipermail/cygwin/2020-February/243855.html
  3) Free from the problem in cgdb and emacs gud.
      https://cygwin.com/pipermail/cygwin/2020-January/243601.html
      https://cygwin.com/pipermail/cygwin/2020-March/244146.html
  4) Redrawing screen on executing native console apps is not necessary.
  5) cygwin-console-helper is not necessary for the pseudo console
     support.
  6) The codes for pseudo console support are much simpler than that
     of the previous one.

  Disadvantages:
  1) The cygwin program which calls console API directly does not work.
  2) The apps which use console API cannot be debugged with gdb. This
     is because pseudo console is not activated since gdb uses
     CreateProcess() rather than exec(). Even with this limitation,
     attaching gdb to native apps, in which pseudo console is already
     activated, works.
  3) Typeahead key inputs are discarded while native console app is
     executed. Simirally, typeahead key inputs while cygwin app is
     executed are not inherited to native console app.
  4) Code page cannot be changed by chcp.com. Acctually, chcp works
     itself and changes code page of its own pseudo console.  However,
     since pseudo console is recreated for another process, it cannot
     inherit the code page.
  5) system_printf() does not work after stderr is closed. (Same with
     cygwin 3.0.7)
  6) Startup time of native console apps is about 3 times slower than
     previous implemenation.
  7) Pseudo console cannot be activated if it is already activated for
     another process on same pty.
2020-08-22 13:43:49 +02:00

308 lines
6.0 KiB
C++

/* 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 "miscfuncs.h"
#include <unistd.h>
#include <utmp.h>
#include <sys/cygwin.h>
#include "cygerrno.h"
#include "security.h"
#include "path.h"
#include "fhandler.h"
#include "dtable.h"
#include "cygheap.h"
#include "pinfo.h"
#include "shared_info.h"
HANDLE NO_COPY tty_list::mutex = NULL;
extern "C" int
getpt (void)
{
return open ("/dev/ptmx", O_RDWR | O_NOCTTY);
}
extern "C" int
posix_openpt (int oflags)
{
return open ("/dev/ptmx", oflags);
}
extern "C" int
grantpt (int fd)
{
cygheap_fdget cfd (fd);
return cfd < 0 ? -1 : 0;
}
extern "C" int
unlockpt (int fd)
{
cygheap_fdget cfd (fd);
return cfd < 0 ? -1 : 0;
}
extern "C" int
revoke (char *ttyname)
{
set_errno (ENOSYS);
return -1;
}
extern "C" int
ttyslot (void)
{
if (myself->ctty <= 0 || iscons_dev (myself->ctty))
return -1;
return device::minor (myself->ctty);
}
void __stdcall
tty_list::init_session ()
{
char mutex_name[MAX_PATH];
char *name = shared_name (mutex_name, "tty_list::mutex", 0);
/* tty_list::mutex is used while searching for a tty slot */
if (!(mutex = CreateMutex (&sec_all_nih, FALSE, name)))
api_fatal ("can't create tty_list::mutex '%s', %E", name);
ProtectHandle (mutex);
}
void __stdcall
tty::init_session ()
{
if (!myself->cygstarted && NOTSTATE (myself, PID_CYGPARENT))
cygheap->fdtab.get_debugger_info ();
}
int __reg2
tty_list::attach (int n)
{
int res;
if (iscons_dev (n))
res = -1;
else if (n != -1)
res = connect (device::minor (n));
else
res = -1;
return res;
}
int
tty_list::connect (int ttynum)
{
if (ttynum < 0 || ttynum >= NTTYS)
{
termios_printf ("ttynum (%d) out of range", ttynum);
return -1;
}
if (!ttys[ttynum].exists ())
{
termios_printf ("pty %d was not allocated", ttynum);
set_errno (ENXIO);
return -1;
}
return ttynum;
}
void
tty_list::init ()
{
for (int i = 0; i < NTTYS; i++)
{
ttys[i].init ();
ttys[i].setntty (DEV_PTYS_MAJOR, i);
}
}
/* Search for a free tty and allocate it.
Return tty number or -1 if error.
*/
int
tty_list::allocate (HANDLE& r, HANDLE& w)
{
lock_ttys here;
int freetty = -1;
tty *t = NULL;
for (int i = 0; i < NTTYS; i++)
if (ttys[i].not_allocated (r, w))
{
t = ttys + i;
t->init ();
t->setsid (0);
freetty = i;
break;
}
if (freetty >= 0)
termios_printf ("pty%d allocated", freetty);
else
{
system_printf ("No pty allocated");
r = w = NULL;
}
return freetty;
}
bool
tty::not_allocated (HANDLE& r, HANDLE& w)
{
/* Attempt to open the from-master side of the tty. If it is accessible
then it exists although we may not have privileges to actually use it. */
char pipename[sizeof("ptyNNNN-from-master")];
__small_sprintf (pipename, "pty%d-from-master", get_minor ());
/* fhandler_pipe::create returns 0 when creation succeeds */
return fhandler_pipe::create (&sec_none, &r, &w,
fhandler_pty_common::pipesize, pipename,
0) == 0;
}
bool
tty::exists ()
{
HANDLE r, w;
bool res;
if (!not_allocated (r, w))
res = true;
else
{
/* Handles are left open when not_allocated finds a non-open "tty" */
CloseHandle (r);
CloseHandle (w);
res = false;
}
debug_printf ("exists %d", res);
return res;
}
bool
tty::slave_alive ()
{
HANDLE ev;
if ((ev = open_inuse (READ_CONTROL)))
CloseHandle (ev);
return ev != NULL;
}
HANDLE
tty::open_mutex (const char *mutex, ACCESS_MASK access)
{
char buf[MAX_PATH];
shared_name (buf, mutex, get_minor ());
return OpenMutex (access, TRUE, buf);
}
HANDLE
tty::open_inuse (ACCESS_MASK access)
{
char buf[MAX_PATH];
shared_name (buf, TTY_SLAVE_ALIVE, get_minor ());
return OpenEvent (access, FALSE, buf);
}
HANDLE
tty::create_inuse (PSECURITY_ATTRIBUTES sa)
{
HANDLE h;
char buf[MAX_PATH];
shared_name (buf, TTY_SLAVE_ALIVE, get_minor ());
h = CreateEvent (sa, TRUE, FALSE, buf);
termios_printf ("%s %p", buf, h);
if (!h)
termios_printf ("couldn't open inuse event %s, %E", buf);
return h;
}
void
tty::init ()
{
output_stopped = 0;
setsid (0);
pgid = 0;
was_opened = false;
master_pid = 0;
is_console = false;
column = 0;
h_pseudo_console = NULL;
switch_to_pcon_in = false;
mask_switch_to_pcon_in = false;
pcon_pid = 0;
term_code_page = 0;
pcon_last_time = 0;
pcon_start = false;
}
HANDLE
tty::get_event (const char *fmt, PSECURITY_ATTRIBUTES sa, BOOL manual_reset)
{
HANDLE hev;
char buf[MAX_PATH];
shared_name (buf, fmt, get_minor ());
if (!sa)
sa = &sec_all;
if (!(hev = CreateEvent (sa, manual_reset, FALSE, buf)))
{
termios_printf ("couldn't create %s", buf);
set_errno (ENOENT); /* FIXME this can't be the right errno */
return NULL;
}
termios_printf ("created event %s", buf);
return hev;
}
lock_ttys::lock_ttys (DWORD howlong): release_me (true)
{
if (WaitForSingleObject (tty_list::mutex, howlong) == WAIT_FAILED)
{
termios_printf ("WFSO for mutex %p failed, %E", tty_list::mutex);
release_me = false;
}
}
void
lock_ttys::release ()
{
ReleaseMutex (tty_list::mutex);
}
const char *
tty_min::ttyname ()
{
device d;
d.parse (ntty);
return d.name ();
}
void
tty::wait_pcon_fwd (void)
{
/* The forwarding in pseudo console sometimes stops for
16-32 msec even if it already has data to transfer.
If the time without transfer exceeds 32 msec, the
forwarding is supposed to be finished. pcon_last_time
is reset to GetTickCount() in pty master forwarding
thread when the last data is transfered. */
const int sleep_in_pcon = 16;
const int time_to_wait = sleep_in_pcon * 2 + 1/* margine */;
pcon_last_time = GetTickCount ();
while (GetTickCount () - pcon_last_time < time_to_wait)
{
int tw = time_to_wait - (GetTickCount () - pcon_last_time);
cygwait (tw);
}
}