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