From 923efaa872b9fe4e9e9cae87effe2af5c338b98a Mon Sep 17 00:00:00 2001 From: Ingo Weinhold Date: Thu, 6 Sep 2007 02:16:25 +0000 Subject: [PATCH] * We store the ID of the controlling terminal and the foreground process group ID with the session and let the terminal update them. * Added an "orphaned" flag to the process_group structure and code to maintain it. * Handle the death of a controlling process correctly: The foreground process group gets a SIGHUP and all newly-orphaned process groups containing at least one stopped processes are sent SIGHUP+SIGCONT. * The tty handles the O_NOCTTY flag correctly, now. * The tty handles reads/writes from processes from other sessions correctly, now. * Handle tcsetpgrp() from background processes correctly. git-svn-id: file:///srv/svn/repos/haiku/haiku/trunk@22187 a95241bf-73f2-0310-859d-f6bbb57e9c96 --- headers/posix/fcntl.h | 2 +- headers/private/kernel/team.h | 3 + headers/private/kernel/thread_types.h | 4 + src/add-ons/kernel/drivers/tty/slave.cpp | 25 ++- src/add-ons/kernel/drivers/tty/tty.cpp | 22 +- src/add-ons/kernel/drivers/tty/tty_private.h | 1 + src/system/kernel/team.cpp | 207 ++++++++++++++++++- 7 files changed, 248 insertions(+), 16 deletions(-) diff --git a/headers/posix/fcntl.h b/headers/posix/fcntl.h index 774dfee3a3..f8c6117f1a 100644 --- a/headers/posix/fcntl.h +++ b/headers/posix/fcntl.h @@ -40,7 +40,7 @@ #define O_EXCL 0x0100 /* exclusive creat */ #define O_CREAT 0x0200 /* create and open file */ #define O_TRUNC 0x0400 /* open with truncation */ -#define O_NOCTTY 0x1000 /* currently unsupported */ +#define O_NOCTTY 0x1000 /* don't make tty the controlling tty */ #define O_NOTRAVERSE 0x2000 /* do not traverse leaf link */ /* flags for open() and fcntl() */ diff --git a/headers/private/kernel/team.h b/headers/private/kernel/team.h index bbd7328b18..ea0046d002 100644 --- a/headers/private/kernel/team.h +++ b/headers/private/kernel/team.h @@ -38,6 +38,9 @@ int32 team_used_teams(void); void team_set_job_control_state(struct team* team, job_control_state newState, int signal, bool threadsLocked); +void team_set_controlling_tty(int32 index); +int32 team_get_controlling_tty(); +status_t team_set_foreground_process_group(int32 ttyIndex, pid_t processGroup); status_t start_watching_team(team_id team, void (*hook)(team_id, void *), void *data); diff --git a/headers/private/kernel/thread_types.h b/headers/private/kernel/thread_types.h index fecefe7213..98a04e69fa 100644 --- a/headers/private/kernel/thread_types.h +++ b/headers/private/kernel/thread_types.h @@ -67,6 +67,9 @@ struct death_entry { struct process_session { pid_t id; int32 group_count; + int32 controlling_tty; // index of the controlling tty, + // -1 if none + pid_t foreground_group; }; struct process_group { @@ -74,6 +77,7 @@ struct process_group { struct process_session *session; pid_t id; struct team *teams; + bool orphaned; }; struct team_loading_info { diff --git a/src/add-ons/kernel/drivers/tty/slave.cpp b/src/add-ons/kernel/drivers/tty/slave.cpp index 563c854c6c..e1cddb292d 100644 --- a/src/add-ons/kernel/drivers/tty/slave.cpp +++ b/src/add-ons/kernel/drivers/tty/slave.cpp @@ -8,6 +8,8 @@ #include +#include + #include "tty_private.h" @@ -41,12 +43,26 @@ slave_open(const char *name, uint32 flags, void **_cookie) if (gMasterTTYs[index].open_count == 0) 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; + status_t status = tty_open(&gSlaveTTYs[index], NULL); if (status < B_OK) { // initializing TTY failed return status; } + } else if (makeControllingTTY) { + // If already open, we allow only processes from the same session + // to open the tty again. + pid_t ttySession = gSlaveTTYs[index].settings->session_id; + if (ttySession < 0 || ttySession != sessionID) + return B_NOT_ALLOWED; } slave_cookie *cookie = (slave_cookie *)malloc(sizeof(struct slave_cookie)); @@ -68,9 +84,16 @@ slave_open(const char *name, uint32 flags, void **_cookie) return status; } - if (gSlaveTTYs[index].open_count == 0) + if (gSlaveTTYs[index].open_count == 0) { gSlaveTTYs[index].lock = gMasterTTYs[index].lock; + if (makeControllingTTY) { + gSlaveTTYs[index].settings->session_id = sessionID; + team_set_controlling_tty(gSlaveTTYs[index].index); + } else + gSlaveTTYs[index].settings->session_id = -1; + } + add_tty_cookie(cookie); *_cookie = cookie; diff --git a/src/add-ons/kernel/drivers/tty/tty.cpp b/src/add-ons/kernel/drivers/tty/tty.cpp index 28aedab359..a6556b80a7 100644 --- a/src/add-ons/kernel/drivers/tty/tty.cpp +++ b/src/add-ons/kernel/drivers/tty/tty.cpp @@ -17,6 +17,8 @@ #include #include +#include + #include "SemaphorePool.h" #include "tty_private.h" @@ -610,7 +612,8 @@ WriterLocker::_CheckBackgroundWrite() const pid_t processGroup = getpgid(0); if (processGroup != fSource->settings->pgrp_id) { - send_signal(-processGroup, SIGTTOU); + if (team_get_controlling_tty() == fSource->index) + send_signal(-processGroup, SIGTTOU); return EIO; } @@ -698,7 +701,8 @@ ReaderLocker::_CheckBackgroundRead() const pid_t processGroup = getpgid(0); if (processGroup != fTTY->settings->pgrp_id) { - send_signal(-processGroup, SIGTTIN); + if (team_get_controlling_tty() == fTTY->index) + send_signal(-processGroup, SIGTTIN); return EIO; } @@ -756,6 +760,7 @@ reset_tty_settings(tty_settings *settings, int32 index) settings->pgrp_id = 0; // this value prevents any signal of being sent + settings->session_id = -1; // some initial window size - the TTY in question should set these values settings->window_size.ws_col = 80; @@ -1321,8 +1326,19 @@ tty_ioctl(tty_cookie *cookie, uint32 op, void *buffer, size_t length) return user_memcpy(buffer, &tty->settings->pgrp_id, sizeof(pid_t)); case TIOCSPGRP: case 'pgid': + { TRACE(("tty: set pgrp_id\n")); - return user_memcpy(&tty->settings->pgrp_id, buffer, sizeof(pid_t)); + 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->index, + groupID); + if (error == B_OK) + tty->settings->pgrp_id = groupID; + return error; + } /* get and set window size */ diff --git a/src/add-ons/kernel/drivers/tty/tty_private.h b/src/add-ons/kernel/drivers/tty/tty_private.h index 8236b01a6b..694417a0ee 100644 --- a/src/add-ons/kernel/drivers/tty/tty_private.h +++ b/src/add-ons/kernel/drivers/tty/tty_private.h @@ -120,6 +120,7 @@ typedef DoublyLinkedList TTYCookieList; struct tty_settings { pid_t pgrp_id; + pid_t session_id; struct termios termios; struct winsize window_size; }; diff --git a/src/system/kernel/team.cpp b/src/system/kernel/team.cpp index 0faabea1f2..0ded65caa1 100644 --- a/src/system/kernel/team.cpp +++ b/src/system/kernel/team.cpp @@ -523,6 +523,7 @@ create_process_group(pid_t id) group->id = id; group->session = NULL; group->teams = NULL; + group->orphaned = true; return group; } @@ -530,12 +531,15 @@ create_process_group(pid_t id) static struct process_session * create_process_session(pid_t id) { - struct process_session *session = (struct process_session *)malloc(sizeof(struct process_session)); + struct process_session *session + = (struct process_session *)malloc(sizeof(struct process_session)); if (session == NULL) return NULL; session->id = id; session->group_count = 0; + session->controlling_tty = -1; + session->foreground_group = -1; return session; } @@ -1480,7 +1484,7 @@ wait_for_child(pid_t child, uint32 flags, int32 *_reason, // If SIGCHLD is blocked, we shall clear pending SIGCHLDs, if no other child // status is available. - if ((atomic_get(&thread->sig_block_mask) & SIGNAL_TO_MASK(SIGCHLD)) != 0) { + if (is_signal_blocked(SIGCHLD)) { InterruptsSpinLocker locker(team_spinlock); if (get_job_control_entry(team, child, flags) == NULL) @@ -1521,6 +1525,57 @@ fill_team_info(struct team *team, team_info *info, size_t size) } +/*! Updates the \c orphaned field of a process_group and returns its new value. + Interrupts must be disabled and team lock be held. +*/ +static bool +update_orphaned_process_group(process_group* group, pid_t dyingProcess) +{ + // Orphaned Process Group: "A process group in which the parent of every + // member is either itself a member of the group or is not a member of the + // group's session." (Open Group Base Specs Issue 6) + + // once orphaned, things won't change (exception: cf. setpgid()) + if (group->orphaned) + return true; + + struct team* team = group->teams; + while (team != NULL) { + struct team* parent = team->parent; + if (team->id != dyingProcess && parent->id != dyingProcess + && parent->group_id != group->id + && parent->session_id == group->session->id) { + return false; + } + + team = team->group_next; + } + + group->orphaned = true; + return true; +} + + +/*! Returns whether the process group contains stopped processes. + Interrupts must be disabled and team lock be held. +*/ +static bool +process_group_has_stopped_processes(process_group* group) +{ + SpinLocker _(thread_spinlock); + + struct team* team = group->teams; + while (team != NULL) { + if (team->main_thread->state == B_THREAD_SUSPENDED) + return true; + + team = team->group_next; + } + + return false; +} + + // #pragma mark - Private kernel API @@ -1683,13 +1738,73 @@ team_delete_process_group(struct process_group *group) } -/** Removes the specified team from the global team hash, and from its parent. - * It also moves all of its children up to the parent. - * You must hold the team lock when you call this function. - * If \a _freeGroup is set to a value other than \c NULL, it must be freed - * from the calling function. - */ +void +team_set_controlling_tty(int32 ttyIndex) +{ + struct team* team = thread_get_current_thread()->team; + InterruptsSpinLocker _(team_spinlock); + + team->group->session->controlling_tty = ttyIndex; + team->group->session->foreground_group = -1; +} + + +int32 +team_get_controlling_tty() +{ + struct team* team = thread_get_current_thread()->team; + + InterruptsSpinLocker _(team_spinlock); + + return team->group->session->controlling_tty; +} + + +status_t +team_set_foreground_process_group(int32 ttyIndex, pid_t processGroupID) +{ + struct thread* thread = thread_get_current_thread(); + struct team* team = thread->team; + + InterruptsSpinLocker locker(team_spinlock); + + process_session* session = team->group->session; + + // must be the controlling tty of the calling process + if (session->controlling_tty != ttyIndex) + return ENOTTY; + + // check process group -- must belong to our session + process_group* group = team_get_process_group_locked(session, + processGroupID); + if (group == NULL) + return B_BAD_VALUE; + + // If we are a background group, we can't do that unharmed, only if we + // ignore or block SIGTTOU. Otherwise the group gets a SIGTTOU. + if (session->foreground_group != -1 + && session->foreground_group != team->group_id + && thread->sig_action[SIGTTOU - 1].sa_handler != SIG_IGN + && !is_signal_blocked(SIGTTOU)) { + pid_t groupID = team->group->id; + locker.Unlock(); + send_signal(-groupID, SIGTTOU); + return B_INTERRUPTED; + } + + team->group->session->foreground_group = processGroupID; + + return B_OK; +} + + +/*! Removes the specified team from the global team hash, and from its parent. + It also moves all of its children up to the parent. + You must hold the team lock when you call this function. + If \a _freeGroup is set to a value other than \c NULL, it must be freed + from the calling function. +*/ void team_remove_team(struct team *team, struct process_group **_freeGroup) { @@ -1706,6 +1821,51 @@ team_remove_team(struct team *team, struct process_group **_freeGroup) team->state = TEAM_STATE_DEATH; + // If we're a controlling process (i.e. a session leader with controlling + // terminal), there's a bit of signalling we have to do. + if (team->session_id == team->id + && team->group->session->controlling_tty >= 0) { + process_session* session = team->group->session; + + session->controlling_tty = -1; + + // send SIGHUP to the foreground + if (session->foreground_group >= 0) { + send_signal_etc(-session->foreground_group, SIGHUP, + SIGNAL_FLAG_TEAMS_LOCKED); + } + + // send SIGHUP + SIGCONT to all newly-orphaned process groups with + // stopped processes + struct team* child = team->children; + while (child != NULL) { + process_group* childGroup = child->group; + if (!childGroup->orphaned + && update_orphaned_process_group(childGroup, team->id) + && process_group_has_stopped_processes(childGroup)) { + send_signal_etc(-childGroup->id, SIGHUP, + SIGNAL_FLAG_TEAMS_LOCKED); + send_signal_etc(-childGroup->id, SIGCONT, + SIGNAL_FLAG_TEAMS_LOCKED); + } + + child = child->siblings_next; + } + } else { + // update "orphaned" flags of all children's process groups + struct team* child = team->children; + while (child != NULL) { + process_group* childGroup = child->group; + if (!childGroup->orphaned) + update_orphaned_process_group(childGroup, team->id); + + child = child->siblings_next; + } + + // update "orphaned" flag of this team's process group + update_orphaned_process_group(team->group, team->id); + } + // reparent each of the team's children reparent_children(team); @@ -2467,6 +2627,10 @@ _user_setpgid(pid_t processID, pid_t groupID) group = create_process_group(groupID); if (group == NULL) return B_NO_MEMORY; + + // The team has a parent in the same session, but in another process + // group, so the new group won't be orphaned. + group->orphaned = false; } status_t status = B_OK; @@ -2489,9 +2653,30 @@ _user_setpgid(pid_t processID, pid_t groupID) struct process_group *targetGroup = team_get_process_group_locked(team->group->session, groupID); if (targetGroup != NULL) { - // we got a group, let's move the team there - remove_team_from_group(team, &freeGroup); - insert_team_into_group(targetGroup, team); + process_group* oldGroup = team->group; + if (targetGroup != oldGroup) { + // we got a group, let's move the team there + remove_team_from_group(team, &freeGroup); + insert_team_into_group(targetGroup, team); + + // Update the "orphaned" flag of all potentially affected + // groups. + + // the team's old group + if (oldGroup->teams != NULL) { + oldGroup->orphaned = false; + update_orphaned_process_group(oldGroup, -1); + } + + // children's groups + struct team* child = team->children; + while (child != NULL) { + child->group->orphaned = false; + update_orphaned_process_group(child->group, -1); + + child = child->siblings_next; + } + } } else status = B_NOT_ALLOWED; }