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; }