* 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
This commit is contained in:
Ingo Weinhold
2007-09-06 02:16:25 +00:00
parent df716df51d
commit 923efaa872
7 changed files with 248 additions and 16 deletions
+1 -1
View File
@@ -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() */
+3
View File
@@ -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);
+4
View File
@@ -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 {
+24 -1
View File
@@ -8,6 +8,8 @@
#include <util/AutoLock.h>
#include <team.h>
#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;
+19 -3
View File
@@ -17,6 +17,8 @@
#include <util/AutoLock.h>
#include <util/kernel_cpp.h>
#include <team.h>
#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 */
@@ -120,6 +120,7 @@ typedef DoublyLinkedList<tty_cookie> TTYCookieList;
struct tty_settings {
pid_t pgrp_id;
pid_t session_id;
struct termios termios;
struct winsize window_size;
};
+196 -11
View File
@@ -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;
}