starnix_core/task/session.rs
1// Copyright 2022 The Fuchsia Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5use crate::device::terminal::Terminal;
6use crate::task::{Pid, ProcessGroup};
7use starnix_sync::{LockDepRwLock, SessionMutableStateLock};
8use starnix_uapi::errors::Errno;
9use starnix_uapi::signals::{SIGCONT, SIGHUP};
10use std::collections::HashSet;
11use std::ops::{Deref, DerefMut};
12use std::sync::Arc;
13
14#[derive(Debug)]
15pub struct SessionMutableState {
16 /// The process groups in the session.
17 ///
18 /// It is expected that these process groups are always valid, as process groups must unregister
19 /// themselves before they are deleted.
20 process_groups: HashSet<Pid>,
21
22 /// The leader of the foreground process group. This is necessary because the leader must
23 /// be returned even if the process group has already been deleted.
24 foreground_process_group: Pid,
25
26 /// The controlling terminal of the session.
27 pub controlling_terminal: Option<ControllingTerminal>,
28}
29
30/// A session is a collection of `ProcessGroup` objects that are related to each other. Each
31/// session has a session ID (`sid`), which is a unique identifier for the session.
32///
33/// The session leader is the first `ProcessGroup` in a session. It is responsible for managing the
34/// session, including sending signals to all processes in the session and controlling the
35/// foreground and background process groups.
36///
37/// When a `ProcessGroup` is created, it is automatically added to the session of its parent.
38/// See `setsid(2)` for information about creating sessions.
39///
40/// A session can be destroyed when the session leader exits or when all process groups in the
41/// session are destroyed.
42#[derive(Debug)]
43pub struct Session {
44 /// The leader of the session
45 pub leader: Pid,
46
47 /// The mutable state of the Session.
48 pub mutable_state: LockDepRwLock<SessionMutableState, SessionMutableStateLock>,
49}
50
51impl PartialEq for Session {
52 fn eq(&self, other: &Self) -> bool {
53 self.leader == other.leader
54 }
55}
56
57impl Session {
58 pub fn new(leader: Pid) -> Arc<Session> {
59 Arc::new(Session {
60 leader: leader.clone(),
61 mutable_state: SessionMutableState {
62 process_groups: HashSet::new(),
63 foreground_process_group: leader,
64 controlling_terminal: None,
65 }
66 .into(),
67 })
68 }
69
70 /// Disassociates the controlling terminal from the session.
71 pub fn disassociate_controlling_terminal(&self) {
72 loop {
73 // THREAD SAFETY: The controlling terminal must be extracted from the Session state
74 // lock. Respect Terminal => Session lock ordering by dropping the Session lock before
75 // acquiring the Terminal lock. The controlling terminal may change while reacquiring
76 // locks.
77 let Some(controlling_terminal) = self.read().controlling_terminal.clone() else {
78 return;
79 };
80 let mut terminal_state = controlling_terminal.terminal.write();
81 let mut state = self.write();
82
83 // THREAD SAFETY: Check whether the controlling terminal changed while the Session lock
84 // was dropped.
85 if !state.controlling_terminal.as_ref().map_or(false, |current_ct| {
86 current_ct.matches(&controlling_terminal.terminal, controlling_terminal.is_main)
87 }) {
88 // Drop the lock for the old terminal and try again.
89 continue;
90 }
91
92 state.controlling_terminal = None;
93 terminal_state.controller = None;
94
95 // THREAD SAFETY: Respect ThreadGroup => Terminal => Session lock ordering by dropping
96 // the Terminal and Session locks before signaling.
97 let process_group = state.get_foreground_process_group();
98 drop(state);
99 drop(terminal_state);
100 if let Ok(pg) = process_group {
101 pg.send_signals(&[SIGHUP, SIGCONT]);
102 }
103 return;
104 }
105 }
106
107 pub fn read(&self) -> impl Deref<Target = SessionMutableState> {
108 self.mutable_state.read()
109 }
110
111 pub fn write(&self) -> impl DerefMut<Target = SessionMutableState> {
112 self.mutable_state.write()
113 }
114}
115
116impl SessionMutableState {
117 /// Removes the process group from the session.
118 pub fn remove(&mut self, leader: &Pid) {
119 self.process_groups.remove(leader);
120 }
121
122 pub fn insert(&mut self, process_group: &Arc<ProcessGroup>) {
123 self.process_groups.insert(process_group.leader.clone());
124 }
125
126 pub fn get_foreground_process_group_leader(&self) -> &Pid {
127 &self.foreground_process_group
128 }
129
130 pub fn get_foreground_process_group(&self) -> Result<Arc<ProcessGroup>, Errno> {
131 self.foreground_process_group.get_process_group()
132 }
133
134 pub fn set_foreground_process_group(&mut self, pgid: &Pid) {
135 self.foreground_process_group = pgid.clone();
136 }
137}
138
139/// The controlling terminal of a session.
140#[derive(Clone, Debug)]
141pub struct ControllingTerminal {
142 /// The controlling terminal.
143 pub terminal: Arc<Terminal>,
144 /// Whether the session is associated to the main or replica side of the terminal.
145 pub is_main: bool,
146}
147
148impl ControllingTerminal {
149 pub fn new(terminal: &Terminal, is_main: bool) -> Self {
150 Self { terminal: terminal.to_owned(), is_main }
151 }
152
153 pub fn matches(&self, terminal: &Terminal, is_main: bool) -> bool {
154 std::ptr::eq(terminal, Arc::as_ptr(&self.terminal)) && is_main == self.is_main
155 }
156}
157
158/// Represents the disassociation of a session's controlling terminal when the session
159/// leader exits.
160///
161/// This struct wraps an optional session and ensures that `disassociate_controlling_terminal`
162/// is explicitly called by the caller, which must be done without holding any
163/// ThreadGroup's write lock.
164#[must_use = "The controlling terminal must be disassociated when the session leader exits."]
165pub struct SessionDisassociation {
166 session: Option<Arc<Session>>,
167}
168
169impl SessionDisassociation {
170 pub(crate) fn new(session: Option<Arc<Session>>) -> Self {
171 Self { session }
172 }
173
174 /// Disassociates the controlling terminal from the session.
175 ///
176 /// If the exiting thread group is the session leader, the controlling terminal must be
177 /// disassociated. This must be called after dropping the ThreadGroup write lock to
178 /// prevent a lock order violation.
179 ///
180 /// Calling it after the thread group has left the process group also ensures that
181 /// the exiting thread group is no longer in the process group when attempting to send
182 /// SIGHUP/SIGCONT to the foreground process group, avoiding a self-deadlock where the
183 /// exiting thread group attempts to write-lock itself.
184 pub fn disassociate_controlling_terminal(self) {
185 if let Some(session) = self.session {
186 session.disassociate_controlling_terminal();
187 }
188 }
189}