From 0c2a5bb5ea94ee2a8f5fe37018e5de315d3d31c6 Mon Sep 17 00:00:00 2001 From: Augustin Cavalier Date: Tue, 9 May 2023 15:47:29 -0400 Subject: [PATCH] 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. --- headers/private/kernel/team.h | 6 +- headers/private/kernel/thread_types.h | 3 +- src/add-ons/kernel/drivers/Jamfile | 3 +- src/add-ons/kernel/drivers/pty/Jamfile | 11 + src/add-ons/kernel/drivers/pty/driver.cpp | 487 ++++++++++++++++++++++ src/add-ons/kernel/generic/tty/tty.cpp | 99 ++++- src/system/kernel/team.cpp | 16 +- 7 files changed, 609 insertions(+), 16 deletions(-) create mode 100644 src/add-ons/kernel/drivers/pty/Jamfile create mode 100644 src/add-ons/kernel/drivers/pty/driver.cpp diff --git a/headers/private/kernel/team.h b/headers/private/kernel/team.h index 8e28007bf1..1c389d9abb 100644 --- a/headers/private/kernel/team.h +++ b/headers/private/kernel/team.h @@ -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 *), diff --git a/headers/private/kernel/thread_types.h b/headers/private/kernel/thread_types.h index c715688b00..0ac367b063 100644 --- a/headers/private/kernel/thread_types.h +++ b/headers/private/kernel/thread_types.h @@ -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: diff --git a/src/add-ons/kernel/drivers/Jamfile b/src/add-ons/kernel/drivers/Jamfile index 96d31f5013..0fb6ac946c 100644 --- a/src/add-ons/kernel/drivers/Jamfile +++ b/src/add-ons/kernel/drivers/Jamfile @@ -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 ; - diff --git a/src/add-ons/kernel/drivers/pty/Jamfile b/src/add-ons/kernel/drivers/pty/Jamfile new file mode 100644 index 0000000000..db636ceeef --- /dev/null +++ b/src/add-ons/kernel/drivers/pty/Jamfile @@ -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 tty : + driver.cpp +; diff --git a/src/add-ons/kernel/drivers/pty/driver.cpp b/src/add-ons/kernel/drivers/pty/driver.cpp new file mode 100644 index 0000000000..5828e22e5f --- /dev/null +++ b/src/add-ons/kernel/drivers/pty/driver.cpp @@ -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 + +#include +#include +#include +#include + +#include +#include + +#include + +extern "C" { +#include +#include +} +#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(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() +}; diff --git a/src/add-ons/kernel/generic/tty/tty.cpp b/src/add-ons/kernel/generic/tty/tty.cpp index fba586bb02..e9b71d83ae 100644 --- a/src/add-ons/kernel/generic/tty/tty.cpp +++ b/src/add-ons/kernel/generic/tty/tty.cpp @@ -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: diff --git a/src/system/kernel/team.cpp b/src/system/kernel/team.cpp index c08c284a28..ddf2b49422 100644 --- a/src/system/kernel/team.cpp +++ b/src/system/kernel/team.cpp @@ -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 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 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 sessionLocker(session); - session->controlling_tty = -1; + session->controlling_tty = NULL; _signalGroup = session->foreground_group; }