Skip to main content

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}