Replace the "tty" driver with a "pty" driver.

This new driver uses the "generic" TTY layer, unlike the old driver
which had its own implementation (which the generic module was derived
from, originally.)

The remaining bits of support for controlling TTYs is added to the kernel &
generic layer at the same time, which should allow for serial interfaces
to be controlling terminals now, as well.

Tested with bash, nano, vim; all seems to still be working as expected.
This commit is contained in:
Augustin Cavalier 2023-05-09 15:47:29 -04:00
parent 7a27ef1439
commit 0c2a5bb5ea
7 changed files with 609 additions and 16 deletions

View File

@ -47,9 +47,9 @@ thread_id load_image_etc(int32 argCount, const char* const* args,
void team_set_job_control_state(Team* team, job_control_state newState,
Signal* signal);
void team_set_controlling_tty(int32 index);
int32 team_get_controlling_tty();
status_t team_set_foreground_process_group(int32 ttyIndex, pid_t processGroup);
void team_set_controlling_tty(void* tty);
void* team_get_controlling_tty();
status_t team_set_foreground_process_group(void *tty, pid_t processGroup);
uid_t team_geteuid(team_id id);
status_t start_watching_team(team_id team, void (*hook)(team_id, void *),

View File

@ -638,8 +638,7 @@ private:
struct ProcessSession : BReferenceable {
pid_t id;
int32 controlling_tty; // index of the controlling tty,
// -1 if none
void* controlling_tty;
pid_t foreground_group;
public:

View File

@ -15,9 +15,8 @@ SubInclude HAIKU_TOP src add-ons kernel drivers misc ;
SubInclude HAIKU_TOP src add-ons kernel drivers network ;
SubInclude HAIKU_TOP src add-ons kernel drivers ports ;
SubInclude HAIKU_TOP src add-ons kernel drivers power ;
SubInclude HAIKU_TOP src add-ons kernel drivers pty ;
SubInclude HAIKU_TOP src add-ons kernel drivers sensor ;
SubInclude HAIKU_TOP src add-ons kernel drivers timer ;
SubInclude HAIKU_TOP src add-ons kernel drivers tty ;
SubInclude HAIKU_TOP src add-ons kernel drivers video ;
SubInclude HAIKU_TOP src add-ons kernel drivers wmi ;

View File

@ -0,0 +1,11 @@
SubDir HAIKU_TOP src add-ons kernel drivers pty ;
UsePrivateKernelHeaders ;
UsePrivateHeaders drivers ;
UseHeaders [ FDirName $(HAIKU_TOP) src add-ons kernel generic tty ] ;
UseHeaders [ FDirName $(HAIKU_TOP) headers os drivers tty ] : true ;
KernelAddon <driver>tty :
driver.cpp
;

View File

@ -0,0 +1,487 @@
/*
* Copyright 2004, Axel Dörfler, axeld@pinc-software.de. All rights reserved.
* Copyright 2023, Haiku, Inc. All rights reserved.
* Distributed under the terms of the MIT License.
*/
#include <new>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <util/AutoLock.h>
#include <Drivers.h>
#include <team.h>
extern "C" {
#include <drivers/tty.h>
#include <tty_module.h>
}
#include "tty_private.h"
//#define PTY_TRACE
#ifdef PTY_TRACE
# define TRACE(x) dprintf x
#else
# define TRACE(x)
#endif
#define DRIVER_NAME "pty"
int32 api_version = B_CUR_DRIVER_API_VERSION;
tty_module_info *gTTYModule = NULL;
struct mutex gGlobalTTYLock;
static const uint32 kNumTTYs = 64;
char *gDeviceNames[kNumTTYs * 2 + 3];
// reserve space for "pt/" and "tt/" entries, "ptmx", "tty",
// and the terminating NULL
struct tty* gMasterTTYs[kNumTTYs];
struct tty* gSlaveTTYs[kNumTTYs];
extern device_hooks gMasterPTYHooks, gSlavePTYHooks;
status_t
init_hardware(void)
{
TRACE((DRIVER_NAME ": init_hardware()\n"));
return B_OK;
}
status_t
init_driver(void)
{
status_t status = get_module(B_TTY_MODULE_NAME, (module_info **)&gTTYModule);
if (status < B_OK)
return status;
TRACE((DRIVER_NAME ": init_driver()\n"));
mutex_init(&gGlobalTTYLock, "tty global");
memset(gDeviceNames, 0, sizeof(gDeviceNames));
memset(gMasterTTYs, 0, sizeof(gMasterTTYs));
memset(gSlaveTTYs, 0, sizeof(gSlaveTTYs));
// create driver name array
char letter = 'p';
int8 digit = 0;
for (uint32 i = 0; i < kNumTTYs; i++) {
// For compatibility, we have to create the same mess in /dev/pt and
// /dev/tt as BeOS does: we publish devices p0, p1, ..., pf, r1, ...,
// sf. It would be nice if we could drop compatibility and create
// something better. In fact we already don't need the master devices
// anymore, since "/dev/ptmx" does the job. The slaves entries could
// be published on the fly when a master is opened (e.g via
// vfs_create_special_node()).
char buffer[64];
snprintf(buffer, sizeof(buffer), "pt/%c%x", letter, digit);
gDeviceNames[i] = strdup(buffer);
snprintf(buffer, sizeof(buffer), "tt/%c%x", letter, digit);
gDeviceNames[i + kNumTTYs] = strdup(buffer);
if (++digit > 15)
digit = 0, letter++;
if (!gDeviceNames[i] || !gDeviceNames[i + kNumTTYs]) {
uninit_driver();
return B_NO_MEMORY;
}
}
gDeviceNames[2 * kNumTTYs] = (char *)"ptmx";
gDeviceNames[2 * kNumTTYs + 1] = (char *)"tty";
return B_OK;
}
void
uninit_driver(void)
{
TRACE((DRIVER_NAME ": uninit_driver()\n"));
for (int32 i = 0; i < (int32)kNumTTYs * 2; i++)
free(gDeviceNames[i]);
mutex_destroy(&gGlobalTTYLock);
put_module(B_TTY_MODULE_NAME);
}
const char **
publish_devices(void)
{
TRACE((DRIVER_NAME ": publish_devices()\n"));
return const_cast<const char **>(gDeviceNames);
}
device_hooks *
find_device(const char *name)
{
TRACE((DRIVER_NAME ": find_device(\"%s\")\n", name));
for (uint32 i = 0; gDeviceNames[i] != NULL; i++) {
if (!strcmp(name, gDeviceNames[i])) {
return i < kNumTTYs || i == (2 * kNumTTYs)
? &gMasterPTYHooks : &gSlavePTYHooks;
}
}
return NULL;
}
static int32
get_tty_index(const char *name)
{
// device names follow this form: "pt/%c%x"
int8 digit = name[4];
if (digit >= 'a') {
// hexadecimal digits
digit -= 'a' - 10;
} else
digit -= '0';
return (name[3] - 'p') * 16 + digit;
}
static int32
get_tty_index(struct tty *tty)
{
int32 index = -1;
for (uint32 i = 0; i < kNumTTYs; i++) {
if (tty == gMasterTTYs[i]) {
index = i;
break;
}
}
return index;
}
// #pragma mark - device hooks
static bool
master_service(struct tty *tty, uint32 op, void *buffer, size_t length)
{
// nothing here yet
return false;
}
static bool
slave_service(struct tty *tty, uint32 op, void *buffer, size_t length)
{
// nothing here yet
return false;
}
static status_t
master_open(const char *name, uint32 flags, void **_cookie)
{
bool findUnusedTTY = strcmp(name, "ptmx") == 0;
int32 index = -1;
if (!findUnusedTTY) {
index = get_tty_index(name);
if (index >= (int32)kNumTTYs)
return B_ERROR;
}
TRACE(("pty_open: TTY index = %" B_PRId32 " (name = %s)\n", index, name));
MutexLocker globalLocker(gGlobalTTYLock);
if (findUnusedTTY) {
for (index = 0; index < (int32)kNumTTYs; index++) {
if (gMasterTTYs[index] == NULL || gMasterTTYs[index]->ref_count == 0)
break;
}
if (index >= (int32)kNumTTYs)
return ENOENT;
} else if (gMasterTTYs[index] != NULL && gMasterTTYs[index]->ref_count != 0) {
// we're already open!
return B_BUSY;
}
if (gMasterTTYs[index] == NULL) {
gMasterTTYs[index] = gTTYModule->tty_create(master_service, NULL);
if (gMasterTTYs[index] == NULL)
return B_NO_MEMORY;
}
if (gSlaveTTYs[index] == NULL) {
gSlaveTTYs[index] = gTTYModule->tty_create(slave_service, gMasterTTYs[index]);
if (gSlaveTTYs[index] == NULL)
return B_NO_MEMORY;
}
tty_cookie *cookie = gTTYModule->tty_create_cookie(gMasterTTYs[index],
gSlaveTTYs[index], flags);
if (cookie == NULL)
return B_NO_MEMORY;
*_cookie = cookie;
return B_OK;
}
static status_t
slave_open(const char *name, uint32 flags, void **_cookie)
{
// Get the tty index: Opening "/dev/tty" means opening the process'
// controlling tty.
int32 index = get_tty_index(name);
if (strcmp(name, "tty") == 0) {
struct tty *controllingTTY = (struct tty *)team_get_controlling_tty();
if (controllingTTY == NULL)
return B_NOT_ALLOWED;
index = get_tty_index(controllingTTY);
if (index < 0)
return B_NOT_ALLOWED;
} else {
index = get_tty_index(name);
if (index >= (int32)kNumTTYs)
return B_ERROR;
}
TRACE(("slave_open: TTY index = %" B_PRId32 " (name = %s)\n", index,
name));
MutexLocker globalLocker(gGlobalTTYLock);
// we may only be used if our master has already been opened
if (gMasterTTYs[index] == NULL || gMasterTTYs[index]->open_count == 0
|| gSlaveTTYs[index] == NULL) {
return B_IO_ERROR;
}
bool makeControllingTTY = (flags & O_NOCTTY) == 0;
pid_t processID = getpid();
pid_t sessionID = getsid(processID);
if (gSlaveTTYs[index]->open_count == 0) {
// We only allow session leaders to open the tty initially.
if (makeControllingTTY && processID != sessionID)
return B_NOT_ALLOWED;
} else if (makeControllingTTY) {
// If already open, we allow only processes from the same session
// to open the tty again while becoming controlling tty
pid_t ttySession = gSlaveTTYs[index]->settings->session_id;
if (ttySession >= 0) {
makeControllingTTY = false;
} else {
// The tty is not associated with a session yet. The process needs
// to be a session leader.
if (makeControllingTTY && processID != sessionID)
return B_NOT_ALLOWED;
}
}
if (gSlaveTTYs[index]->open_count == 0) {
gSlaveTTYs[index]->settings->session_id = -1;
gSlaveTTYs[index]->settings->pgrp_id = -1;
}
tty_cookie *cookie = gTTYModule->tty_create_cookie(gSlaveTTYs[index],
gMasterTTYs[index], flags);
if (cookie == NULL) {
gSlaveTTYs[index] = NULL;
return B_NO_MEMORY;
}
if (makeControllingTTY) {
gSlaveTTYs[index]->settings->session_id = sessionID;
gSlaveTTYs[index]->settings->pgrp_id = sessionID;
team_set_controlling_tty(gSlaveTTYs[index]);
}
*_cookie = cookie;
return B_OK;
}
static status_t
pty_close(void *_cookie)
{
tty_cookie *cookie = (tty_cookie *)_cookie;
TRACE(("pty_close: cookie %p\n", _cookie));
MutexLocker globalLocker(gGlobalTTYLock);
gTTYModule->tty_close_cookie(cookie);
return B_OK;
}
static status_t
pty_free_cookie(void *_cookie)
{
// The TTY is already closed. We only have to free the cookie.
tty_cookie *cookie = (tty_cookie *)_cookie;
gTTYModule->tty_destroy_cookie(cookie);
return B_OK;
}
static status_t
pty_ioctl(void *_cookie, uint32 op, void *buffer, size_t length)
{
tty_cookie *cookie = (tty_cookie *)_cookie;
struct tty* tty = cookie->tty;
RecursiveLocker locker(tty->lock);
TRACE(("pty_ioctl: cookie %p, op %" B_PRIu32 ", buffer %p, length %lu"
"\n", _cookie, op, buffer, length));
switch (op) {
case B_IOCTL_GET_TTY_INDEX:
{
int32 ptyIndex = get_tty_index(cookie->tty);
if (ptyIndex < 0)
return B_BAD_VALUE;
if (user_memcpy(buffer, &ptyIndex, sizeof(int32)) < B_OK)
return B_BAD_ADDRESS;
return B_OK;
}
case B_IOCTL_GRANT_TTY:
{
if (!cookie->tty->is_master)
return B_BAD_VALUE;
int32 ptyIndex = get_tty_index(cookie->tty);
if (ptyIndex < 0)
return B_BAD_VALUE;
// get slave path
char path[64];
snprintf(path, sizeof(path), "/dev/%s",
gDeviceNames[kNumTTYs + ptyIndex]);
// set owner and permissions respectively
if (chown(path, getuid(), getgid()) != 0
|| chmod(path, S_IRUSR | S_IWUSR | S_IWGRP) != 0) {
return errno;
}
return B_OK;
}
case 'pgid': // BeOS
op = TIOCSPGRP;
case 'wsiz': // BeOS
op = TIOCSWINSZ;
break;
default:
break;
}
return gTTYModule->tty_control(cookie, op, buffer, length);
}
static status_t
pty_read(void *_cookie, off_t offset, void *buffer, size_t *_length)
{
tty_cookie *cookie = (tty_cookie *)_cookie;
TRACE(("pty_read: cookie %p, offset %" B_PRIdOFF ", buffer %p, length "
"%lu\n", _cookie, offset, buffer, *_length));
status_t result = gTTYModule->tty_read(cookie, buffer, _length);
TRACE(("pty_read done: cookie %p, result: %" B_PRIx32 ", length %lu\n",
_cookie, result, *_length));
return result;
}
static status_t
pty_write(void *_cookie, off_t offset, const void *buffer, size_t *_length)
{
tty_cookie *cookie = (tty_cookie *)_cookie;
TRACE(("pty_write: cookie %p, offset %" B_PRIdOFF ", buffer %p, length "
"%lu\n", _cookie, offset, buffer, *_length));
status_t result = gTTYModule->tty_write(cookie, buffer, _length);
TRACE(("pty_write done: cookie %p, result: %" B_PRIx32 ", length %lu\n",
_cookie, result, *_length));
return result;
}
static status_t
pty_select(void *_cookie, uint8 event, uint32 ref, selectsync *sync)
{
tty_cookie *cookie = (tty_cookie *)_cookie;
return gTTYModule->tty_select(cookie, event, ref, sync);
}
static status_t
pty_deselect(void *_cookie, uint8 event, selectsync *sync)
{
tty_cookie *cookie = (tty_cookie *)_cookie;
return gTTYModule->tty_deselect(cookie, event, sync);
}
device_hooks gMasterPTYHooks = {
&master_open,
&pty_close,
&pty_free_cookie,
&pty_ioctl,
&pty_read,
&pty_write,
&pty_select,
&pty_deselect,
NULL, // read_pages()
NULL // write_pages()
};
device_hooks gSlavePTYHooks = {
&slave_open,
&pty_close,
&pty_free_cookie,
&pty_ioctl,
&pty_read,
&pty_write,
&pty_select,
&pty_deselect,
NULL, // read_pages()
NULL // write_pages()
};

View File

@ -115,6 +115,7 @@ public:
private:
size_t _CheckAvailableBytes() const;
status_t _CheckBackgroundWrite() const;
struct tty* fSource;
struct tty* fTarget;
@ -133,6 +134,7 @@ public:
private:
size_t _CheckAvailableBytes() const;
status_t _CheckBackgroundRead() const;
struct tty* fTTY;
RequestOwner fRequestOwner;
@ -598,6 +600,9 @@ WriterLocker::AcquireWriter(bool dontBlock, size_t bytesNeeded)
status = fRequestOwner.Error();
}
if (status == B_OK)
status = _CheckBackgroundWrite();
if (status == B_OK)
fBytes = _CheckAvailableBytes();
@ -605,6 +610,26 @@ WriterLocker::AcquireWriter(bool dontBlock, size_t bytesNeeded)
}
status_t
WriterLocker::_CheckBackgroundWrite() const
{
// only relevant for the slave end and only when TOSTOP is set
if (fSource->is_master
|| (fSource->settings->termios.c_lflag & TOSTOP) == 0) {
return B_OK;
}
pid_t processGroup = getpgid(0);
if (fSource->settings->pgrp_id != 0
&& processGroup != fSource->settings->pgrp_id) {
if (team_get_controlling_tty() == fSource)
send_signal(-processGroup, SIGTTOU);
}
return B_OK;
}
// #pragma mark -
@ -646,6 +671,10 @@ ReaderLocker::AcquireReader(bigtime_t timeout, size_t bytesNeeded)
if (fCookie->closed)
return B_FILE_ERROR;
status_t status = _CheckBackgroundRead();
if (status != B_OK)
return status;
// check, if we're first in queue, and if there is something to read
if (fRequestOwner.IsFirstInQueues()) {
fBytes = _CheckAvailableBytes();
@ -670,9 +699,12 @@ ReaderLocker::AcquireReader(bigtime_t timeout, size_t bytesNeeded)
// block until something happens
Unlock();
status_t status = fRequestOwner.Wait(true, timeout);
status = fRequestOwner.Wait(true, timeout);
Lock();
if (status == B_OK)
status = _CheckBackgroundRead();
fBytes = _CheckAvailableBytes();
TRACE(("ReaderLocker::AcquireReader() ended status 0x%" B_PRIx32 "\n",
@ -697,6 +729,24 @@ ReaderLocker::_CheckAvailableBytes() const
}
status_t
ReaderLocker::_CheckBackgroundRead() const
{
// only relevant for the slave end
if (fTTY->is_master)
return B_OK;
pid_t processGroup = getpgid(0);
if (fTTY->settings->pgrp_id != 0
&& processGroup != fTTY->settings->pgrp_id) {
if (team_get_controlling_tty() == fTTY)
send_signal(-processGroup, SIGTTIN);
}
return B_OK;
}
// #pragma mark -
@ -1652,6 +1702,53 @@ tty_control(tty_cookie* cookie, uint32 op, void* buffer, size_t length)
return status;
}
// get and set process group ID
case TIOCGPGRP:
TRACE(("tty: get pgrp_id\n"));
return user_memcpy(buffer, &tty->settings->pgrp_id, sizeof(pid_t));
case TIOCSPGRP:
{
TRACE(("tty: set pgrp_id\n"));
pid_t groupID;
if (user_memcpy(&groupID, buffer, sizeof(pid_t)) != B_OK)
return B_BAD_ADDRESS;
status_t error = team_set_foreground_process_group(tty,
groupID);
if (error == B_OK)
tty->settings->pgrp_id = groupID;
return error;
}
// become controlling TTY
case TIOCSCTTY:
{
TRACE(("tty: become controlling tty\n"));
pid_t processID = getpid();
pid_t sessionID = getsid(processID);
// Only session leaders can become controlling tty
if (processID != sessionID)
return B_NOT_ALLOWED;
// Check if already controlling tty
if (team_get_controlling_tty() == tty)
return B_OK;
tty->settings->session_id = sessionID;
tty->settings->pgrp_id = sessionID;
team_set_controlling_tty(tty);
return B_OK;
}
// get session leader process group ID
case TIOCGSID:
{
TRACE(("tty: get session_id\n"));
return user_memcpy(buffer, &tty->settings->session_id,
sizeof(pid_t));
}
// get and set window size
case TIOCGWINSZ:

View File

@ -1117,7 +1117,7 @@ ProcessGroup::UnsetOrphanedCheck()
ProcessSession::ProcessSession(pid_t id)
:
id(id),
controlling_tty(-1),
controlling_tty(NULL),
foreground_group(-1)
{
char lockName[32];
@ -2988,7 +2988,7 @@ team_get_team_struct_locked(team_id id)
void
team_set_controlling_tty(int32 ttyIndex)
team_set_controlling_tty(void* tty)
{
// lock the team, so its session won't change while we're playing with it
Team* team = thread_get_current_thread()->team;
@ -2999,12 +2999,12 @@ team_set_controlling_tty(int32 ttyIndex)
AutoLocker<ProcessSession> sessionLocker(session);
// set the session's fields
session->controlling_tty = ttyIndex;
session->controlling_tty = tty;
session->foreground_group = -1;
}
int32
void*
team_get_controlling_tty()
{
// lock the team, so its session won't change while we're playing with it
@ -3021,7 +3021,7 @@ team_get_controlling_tty()
status_t
team_set_foreground_process_group(int32 ttyIndex, pid_t processGroupID)
team_set_foreground_process_group(void* tty, pid_t processGroupID)
{
// lock the team, so its session won't change while we're playing with it
Thread* thread = thread_get_current_thread();
@ -3033,7 +3033,7 @@ team_set_foreground_process_group(int32 ttyIndex, pid_t processGroupID)
AutoLocker<ProcessSession> sessionLocker(session);
// check given TTY -- must be the controlling tty of the calling process
if (session->controlling_tty != ttyIndex)
if (session->controlling_tty != tty)
return ENOTTY;
// check given process group -- must belong to our session
@ -3121,14 +3121,14 @@ team_remove_team(Team* team, pid_t& _signalGroup)
_signalGroup = -1;
bool isSessionLeader = false;
if (team->session_id == team->id
&& team->group->Session()->controlling_tty >= 0) {
&& team->group->Session()->controlling_tty != NULL) {
isSessionLeader = true;
ProcessSession* session = team->group->Session();
AutoLocker<ProcessSession> sessionLocker(session);
session->controlling_tty = -1;
session->controlling_tty = NULL;
_signalGroup = session->foreground_group;
}