⚠️ VeridianOS Kernel Documentation - This is low-level kernel code. All functions are unsafe unless explicitly marked otherwise. no_std

veridian_kernel/fs/
pty.rs

1//! Pseudo-Terminal (PTY) Support
2//!
3//! Provides pseudo-terminal devices for terminal emulation and shell
4//! interaction.
5//!
6//! # Architecture
7//!
8//! A PTY pair consists of a master and a slave side. The master is used by a
9//! terminal emulator (or the graphical desktop renderer); the slave is used by
10//! the shell or application running inside the terminal.
11//!
12//! Data flow:
13//! - Master writes to `input_buffer`  → slave reads from `input_buffer`
14//! - Slave  writes to `output_buffer` → master reads from `output_buffer`
15//!
16//! The [`PtyMasterNode`] and [`PtySlaveNode`] VfsNode wrappers expose these
17//! buffers as regular file descriptors so that standard `read`/`write`
18//! syscalls work transparently.
19
20// Allow dead code for PTY fields not yet used in current implementation
21#![allow(dead_code, clippy::needless_range_loop)]
22
23#[allow(unused_imports)]
24use alloc::{collections::VecDeque, format, sync::Arc, vec::Vec};
25use core::sync::atomic::{AtomicU32, AtomicU64, Ordering};
26
27#[cfg(not(target_arch = "aarch64"))]
28use spin::RwLock;
29
30#[cfg(target_arch = "aarch64")]
31use super::bare_lock::RwLock;
32use crate::{error::KernelError, process::ProcessId, sync::once_lock::GlobalState};
33
34/// PTY buffer size
35const PTY_BUFFER_SIZE: usize = 4096;
36
37/// Terminal control characters
38pub mod termios {
39    pub const VINTR: u8 = 0; // ^C
40    pub const VQUIT: u8 = 1; // ^\
41    pub const VERASE: u8 = 2; // Backspace
42    pub const VKILL: u8 = 3; // ^U
43    pub const VEOF: u8 = 4; // ^D
44    pub const VEOL: u8 = 5; // End of line
45    pub const VSUSP: u8 = 6; // ^Z
46    pub const VINTR_CHAR: u8 = 3; // ASCII ^C
47    pub const VEOF_CHAR: u8 = 4; // ASCII ^D
48    pub const VSUSP_CHAR: u8 = 26; // ASCII ^Z (0x1A)
49}
50
51/// PTY Terminal mode flags
52#[derive(Debug, Clone, Copy)]
53pub struct TermiosFlags {
54    /// Echo input characters
55    pub echo: bool,
56
57    /// Canonical mode (line buffering)
58    pub canonical: bool,
59
60    /// Enable signals
61    pub isig: bool,
62
63    /// Process output (convert \n to \r\n)
64    pub opost: bool,
65}
66
67impl Default for TermiosFlags {
68    fn default() -> Self {
69        Self {
70            echo: true,
71            canonical: true,
72            isig: true,
73            opost: true,
74        }
75    }
76}
77
78/// Window size information
79#[derive(Debug, Clone, Copy)]
80pub struct Winsize {
81    pub rows: u16,
82    pub cols: u16,
83    pub xpixel: u16,
84    pub ypixel: u16,
85}
86
87impl Default for Winsize {
88    fn default() -> Self {
89        Self {
90            rows: 24,
91            cols: 80,
92            xpixel: 0,
93            ypixel: 0,
94        }
95    }
96}
97
98/// PTY Master side (controlled by terminal emulator)
99pub struct PtyMaster {
100    /// PTY ID
101    id: u32,
102
103    /// Input buffer (from master to slave)
104    input_buffer: RwLock<VecDeque<u8>>,
105
106    /// Output buffer (from slave to master)
107    output_buffer: RwLock<VecDeque<u8>>,
108
109    /// Window size
110    winsize: RwLock<Winsize>,
111
112    /// Terminal flags
113    flags: RwLock<TermiosFlags>,
114
115    /// Process ID of controlling process
116    controller: RwLock<Option<ProcessId>>,
117
118    /// Foreground process group ID for this terminal.
119    /// Processes in this group receive keyboard-generated signals (SIGINT,
120    /// SIGTSTP). Background processes accessing this terminal receive
121    /// SIGTTIN/SIGTTOU.
122    foreground_pgid: AtomicU64,
123}
124
125impl PtyMaster {
126    /// Create a new PTY master
127    pub fn new(id: u32) -> Self {
128        Self {
129            id,
130            input_buffer: RwLock::new(VecDeque::with_capacity(PTY_BUFFER_SIZE)),
131            output_buffer: RwLock::new(VecDeque::with_capacity(PTY_BUFFER_SIZE)),
132            winsize: RwLock::new(Winsize::default()),
133            flags: RwLock::new(TermiosFlags::default()),
134            controller: RwLock::new(None),
135            foreground_pgid: AtomicU64::new(0),
136        }
137    }
138
139    /// Get PTY ID
140    pub fn id(&self) -> u32 {
141        self.id
142    }
143
144    /// Read from slave output (what the slave wrote)
145    pub fn read(&self, buffer: &mut [u8]) -> Result<usize, KernelError> {
146        let mut output = self.output_buffer.write();
147        let bytes_to_read = buffer.len().min(output.len());
148
149        for i in 0..bytes_to_read {
150            // bytes_to_read <= output.len(), so pop_front cannot return None
151            buffer[i] = output.pop_front().expect("output buffer underrun");
152        }
153
154        Ok(bytes_to_read)
155    }
156
157    /// Write to slave input (what the slave will read)
158    pub fn write(&self, data: &[u8]) -> Result<usize, KernelError> {
159        let mut input = self.input_buffer.write();
160        let flags = self.flags.read();
161
162        for &byte in data {
163            // Handle special characters if signals are enabled
164            if flags.isig {
165                if byte == termios::VINTR_CHAR {
166                    // Send SIGINT to foreground process group (^C)
167                    self.send_signal_to_foreground_group(2);
168                    continue;
169                }
170
171                if byte == termios::VSUSP_CHAR {
172                    // Send SIGTSTP to foreground process group (^Z)
173                    self.send_signal_to_foreground_group(20);
174                    continue;
175                }
176            }
177
178            if input.len() < PTY_BUFFER_SIZE {
179                input.push_back(byte);
180            } else {
181                return Err(KernelError::ResourceExhausted {
182                    resource: "pty_input_buffer",
183                });
184            }
185        }
186
187        Ok(data.len())
188    }
189
190    /// Send a signal to all processes in the foreground process group.
191    ///
192    /// Falls back to the controlling process if no foreground group is set.
193    fn send_signal_to_foreground_group(&self, signal: i32) {
194        let fg_pgid = self.foreground_pgid.load(Ordering::Acquire);
195
196        if fg_pgid != 0 {
197            // Send signal to all processes in the foreground process group
198            crate::process::table::PROCESS_TABLE.for_each(|proc| {
199                let proc_pgid = proc.pgid.load(Ordering::Acquire);
200                if proc_pgid == fg_pgid && proc.is_alive() {
201                    if let Err(_e) = proc.send_signal(signal as usize) {
202                        crate::println!(
203                            "[PTY] Warning: failed to send signal {} to PID {}: {:?}",
204                            signal,
205                            proc.pid.0,
206                            _e
207                        );
208                    }
209                }
210            });
211        } else if let Some(pid) = *self.controller.read() {
212            // Fallback: send to controlling process only
213            let process_server = crate::services::process_server::get_process_server();
214            if let Err(_e) = process_server.send_signal(pid, signal) {
215                crate::println!(
216                    "[PTY] Warning: failed to send signal {} to PID {}: {:?}",
217                    signal,
218                    pid.0,
219                    _e
220                );
221            }
222        }
223    }
224
225    /// Set window size
226    pub fn set_winsize(&self, winsize: Winsize) {
227        *self.winsize.write() = winsize;
228        // Send SIGWINCH to controlling process
229        if let Some(pid) = *self.controller.read() {
230            let process_server = crate::services::process_server::get_process_server();
231            if let Err(_e) = process_server.send_signal(pid, 28) {
232                crate::println!(
233                    "[PTY] Warning: failed to send SIGWINCH to PID {}: {:?}",
234                    pid.0,
235                    _e
236                );
237            }
238        }
239    }
240
241    /// Get window size
242    pub fn get_winsize(&self) -> Winsize {
243        *self.winsize.read()
244    }
245
246    /// Set terminal flags
247    pub fn set_flags(&self, flags: TermiosFlags) {
248        *self.flags.write() = flags;
249    }
250
251    /// Get terminal flags
252    pub fn get_flags(&self) -> TermiosFlags {
253        *self.flags.read()
254    }
255
256    /// Set the controlling process for this PTY.
257    ///
258    /// The controlling process receives signals (SIGINT, SIGTSTP, SIGWINCH)
259    /// generated by special terminal characters (^C, ^Z) and window size
260    /// changes.
261    pub fn set_controller(&self, pid: ProcessId) {
262        *self.controller.write() = Some(pid);
263    }
264
265    /// Get the controlling process ID, if any.
266    pub fn get_controller(&self) -> Option<ProcessId> {
267        *self.controller.read()
268    }
269
270    /// Set the foreground process group ID for this terminal.
271    ///
272    /// The foreground process group receives keyboard-generated signals
273    /// (SIGINT from ^C, SIGTSTP from ^Z). Processes in background groups
274    /// that attempt to read from or write to this terminal receive
275    /// SIGTTIN or SIGTTOU respectively.
276    pub fn set_foreground_pgid(&self, pgid: u64) {
277        self.foreground_pgid.store(pgid, Ordering::Release);
278    }
279
280    /// Get the foreground process group ID for this terminal.
281    ///
282    /// Returns 0 if no foreground group has been set.
283    pub fn get_foreground_pgid(&self) -> u64 {
284        self.foreground_pgid.load(Ordering::Acquire)
285    }
286}
287
288/// PTY Slave side (used by shell/application)
289pub struct PtySlave {
290    /// PTY ID
291    id: u32,
292
293    /// Reference to master (for accessing buffers)
294    master_id: u32,
295}
296
297impl PtySlave {
298    /// Create a new PTY slave
299    pub fn new(id: u32, master_id: u32) -> Self {
300        Self { id, master_id }
301    }
302
303    /// Get PTY ID
304    pub fn id(&self) -> u32 {
305        self.id
306    }
307
308    /// Get master ID
309    pub fn master_id(&self) -> u32 {
310        self.master_id
311    }
312
313    /// Read from master (what user typed)
314    pub fn read(&self, buffer: &mut [u8]) -> Result<usize, KernelError> {
315        // Get master and read from input buffer
316        if let Some(master) = get_pty_master(self.master_id) {
317            let mut input = master.input_buffer.write();
318            let bytes_to_read = buffer.len().min(input.len());
319
320            for i in 0..bytes_to_read {
321                // bytes_to_read <= input.len(), so pop_front cannot return None
322                buffer[i] = input.pop_front().expect("input buffer underrun");
323            }
324
325            Ok(bytes_to_read)
326        } else {
327            Err(KernelError::NotFound {
328                resource: "pty_master",
329                id: self.master_id as u64,
330            })
331        }
332    }
333
334    /// Write to master (output to terminal)
335    pub fn write(&self, data: &[u8]) -> Result<usize, KernelError> {
336        // Get master and write to output buffer
337        if let Some(master) = get_pty_master(self.master_id) {
338            let mut output = master.output_buffer.write();
339            let flags = master.flags.read();
340
341            for &byte in data {
342                // Process output if opost is enabled
343                if flags.opost && byte == b'\n' {
344                    // Convert \n to \r\n
345                    if output.len() < PTY_BUFFER_SIZE - 1 {
346                        output.push_back(b'\r');
347                        output.push_back(b'\n');
348                    }
349                } else if output.len() < PTY_BUFFER_SIZE {
350                    output.push_back(byte);
351                } else {
352                    return Err(KernelError::ResourceExhausted {
353                        resource: "pty_output_buffer",
354                    });
355                }
356            }
357
358            Ok(data.len())
359        } else {
360            Err(KernelError::NotFound {
361                resource: "pty_master",
362                id: self.master_id as u64,
363            })
364        }
365    }
366}
367
368/// PTY Manager for creating and managing PTY pairs
369pub struct PtyManager {
370    /// All PTY masters (with interior mutability and Arc for sharing)
371    masters: RwLock<Vec<Arc<PtyMaster>>>,
372
373    /// Next PTY ID (atomic for thread-safety)
374    next_id: AtomicU32,
375}
376
377impl PtyManager {
378    /// Create a new PTY manager
379    pub fn new() -> Self {
380        Self {
381            masters: RwLock::new(Vec::new()),
382            next_id: AtomicU32::new(0),
383        }
384    }
385
386    /// Create a new PTY pair
387    pub fn create_pty(&self) -> Result<(u32, u32), KernelError> {
388        let id = self.next_id.fetch_add(1, Ordering::Relaxed);
389
390        let master = Arc::new(PtyMaster::new(id));
391        let master_id = master.id();
392
393        self.masters.write().push(master);
394
395        println!(
396            "[PTY] Created PTY pair: master={}, slave={}",
397            master_id, master_id
398        );
399
400        Ok((master_id, master_id))
401    }
402
403    /// Get PTY master by ID
404    pub fn get_master(&self, id: u32) -> Option<Arc<PtyMaster>> {
405        self.masters.read().iter().find(|m| m.id == id).cloned()
406    }
407
408    /// Check if PTY exists
409    pub fn has_pty(&self, id: u32) -> bool {
410        self.masters.read().iter().any(|m| m.id == id)
411    }
412
413    /// Close a PTY
414    pub fn close_pty(&self, id: u32) -> Result<(), KernelError> {
415        let mut masters = self.masters.write();
416        if let Some(pos) = masters.iter().position(|m| m.id == id) {
417            masters.remove(pos);
418            println!("[PTY] Closed PTY {}", id);
419            Ok(())
420        } else {
421            Err(KernelError::NotFound {
422                resource: "pty",
423                id: id as u64,
424            })
425        }
426    }
427
428    /// Get number of active PTYs
429    pub fn count(&self) -> usize {
430        self.masters.read().len()
431    }
432}
433
434impl Default for PtyManager {
435    fn default() -> Self {
436        Self::new()
437    }
438}
439
440/// Global PTY manager
441static PTY_MANAGER: GlobalState<PtyManager> = GlobalState::new();
442
443/// Initialize PTY system
444pub fn init() -> Result<(), KernelError> {
445    let manager = PtyManager::new();
446    PTY_MANAGER
447        .init(manager)
448        .map_err(|_| KernelError::InvalidState {
449            expected: "uninitialized",
450            actual: "initialized",
451        })?;
452
453    println!("[PTY] Pseudo-terminal system initialized");
454    Ok(())
455}
456
457/// Execute a function with the PTY manager
458pub fn with_pty_manager<R, F: FnOnce(&PtyManager) -> R>(f: F) -> Option<R> {
459    PTY_MANAGER.with(f)
460}
461
462/// Helper function to get PTY master
463fn get_pty_master(id: u32) -> Option<Arc<PtyMaster>> {
464    PTY_MANAGER.with(|manager| manager.get_master(id)).flatten()
465}
466
467// ============================================================================
468// VfsNode wrappers for PTY file descriptors
469// ============================================================================
470
471use super::{DirEntry, Metadata, NodeType, Permissions, VfsNode};
472
473/// VfsNode adapter for the master side of a PTY.
474///
475/// Reading from this node returns bytes that the slave has written (i.e. the
476/// program's output). Writing to this node delivers bytes to the slave's input
477/// buffer (i.e. simulates keyboard input).
478pub struct PtyMasterNode {
479    /// The underlying PTY master, shared with the slave view.
480    master: Arc<PtyMaster>,
481}
482
483impl PtyMasterNode {
484    /// Wrap an existing [`PtyMaster`] as a VfsNode.
485    pub fn new(master: Arc<PtyMaster>) -> Self {
486        Self { master }
487    }
488
489    /// Return the PTY ID owned by this master node.
490    pub fn pty_id(&self) -> u32 {
491        self.master.id()
492    }
493}
494
495impl VfsNode for PtyMasterNode {
496    fn node_type(&self) -> NodeType {
497        NodeType::CharDevice
498    }
499
500    /// Read bytes produced by the slave (the program's stdout/stderr).
501    fn read(&self, _offset: usize, buffer: &mut [u8]) -> Result<usize, KernelError> {
502        self.master.read(buffer)
503    }
504
505    /// Write bytes that the slave will read (simulate keyboard input).
506    fn write(&self, _offset: usize, data: &[u8]) -> Result<usize, KernelError> {
507        self.master.write(data)
508    }
509
510    fn metadata(&self) -> Result<Metadata, KernelError> {
511        Ok(Metadata {
512            node_type: NodeType::CharDevice,
513            size: 0,
514            permissions: Permissions::from_mode(0o620),
515            uid: 0,
516            gid: 5, // tty group
517            created: 0,
518            modified: 0,
519            accessed: 0,
520            inode: 0x9000_0000 | self.master.id() as u64,
521        })
522    }
523
524    fn readdir(&self) -> Result<Vec<DirEntry>, KernelError> {
525        Err(KernelError::FsError(crate::error::FsError::NotADirectory))
526    }
527
528    fn lookup(&self, _name: &str) -> Result<Arc<dyn VfsNode>, KernelError> {
529        Err(KernelError::FsError(crate::error::FsError::NotADirectory))
530    }
531
532    fn create(
533        &self,
534        _name: &str,
535        _permissions: Permissions,
536    ) -> Result<Arc<dyn VfsNode>, KernelError> {
537        Err(KernelError::FsError(crate::error::FsError::NotADirectory))
538    }
539
540    fn mkdir(
541        &self,
542        _name: &str,
543        _permissions: Permissions,
544    ) -> Result<Arc<dyn VfsNode>, KernelError> {
545        Err(KernelError::FsError(crate::error::FsError::NotADirectory))
546    }
547
548    fn unlink(&self, _name: &str) -> Result<(), KernelError> {
549        Err(KernelError::FsError(crate::error::FsError::NotADirectory))
550    }
551
552    fn truncate(&self, _size: usize) -> Result<(), KernelError> {
553        Err(KernelError::PermissionDenied {
554            operation: "truncate PTY master",
555        })
556    }
557}
558
559/// VfsNode adapter for the slave side of a PTY.
560///
561/// Reading from this node returns bytes that the master has written (i.e.
562/// keyboard input forwarded to the shell). Writing to this node sends bytes
563/// to the master's output buffer (the program's output visible in the
564/// terminal emulator).
565pub struct PtySlaveNode {
566    /// The slave descriptor, which back-references the master by ID.
567    slave: PtySlave,
568}
569
570impl PtySlaveNode {
571    /// Wrap a [`PtySlave`] as a VfsNode.
572    pub fn new(slave: PtySlave) -> Self {
573        Self { slave }
574    }
575
576    /// Return the PTY ID of this slave.
577    pub fn pty_id(&self) -> u32 {
578        self.slave.id()
579    }
580
581    /// Return the path that would be exposed as the slave device name.
582    pub fn pts_path(&self) -> alloc::string::String {
583        format!("/dev/pts/{}", self.slave.id())
584    }
585}
586
587impl VfsNode for PtySlaveNode {
588    fn node_type(&self) -> NodeType {
589        NodeType::CharDevice
590    }
591
592    /// Read bytes that the master wrote (keyboard input / program stdin).
593    fn read(&self, _offset: usize, buffer: &mut [u8]) -> Result<usize, KernelError> {
594        self.slave.read(buffer)
595    }
596
597    /// Write bytes that the master will read (program output / terminal
598    /// output).
599    fn write(&self, _offset: usize, data: &[u8]) -> Result<usize, KernelError> {
600        self.slave.write(data)
601    }
602
603    fn metadata(&self) -> Result<Metadata, KernelError> {
604        Ok(Metadata {
605            node_type: NodeType::CharDevice,
606            size: 0,
607            permissions: Permissions::from_mode(0o620),
608            uid: 0,
609            gid: 5, // tty group
610            created: 0,
611            modified: 0,
612            accessed: 0,
613            inode: 0x9100_0000 | self.slave.id() as u64,
614        })
615    }
616
617    fn readdir(&self) -> Result<Vec<DirEntry>, KernelError> {
618        Err(KernelError::FsError(crate::error::FsError::NotADirectory))
619    }
620
621    fn lookup(&self, _name: &str) -> Result<Arc<dyn VfsNode>, KernelError> {
622        Err(KernelError::FsError(crate::error::FsError::NotADirectory))
623    }
624
625    fn create(
626        &self,
627        _name: &str,
628        _permissions: Permissions,
629    ) -> Result<Arc<dyn VfsNode>, KernelError> {
630        Err(KernelError::FsError(crate::error::FsError::NotADirectory))
631    }
632
633    fn mkdir(
634        &self,
635        _name: &str,
636        _permissions: Permissions,
637    ) -> Result<Arc<dyn VfsNode>, KernelError> {
638        Err(KernelError::FsError(crate::error::FsError::NotADirectory))
639    }
640
641    fn unlink(&self, _name: &str) -> Result<(), KernelError> {
642        Err(KernelError::FsError(crate::error::FsError::NotADirectory))
643    }
644
645    fn truncate(&self, _size: usize) -> Result<(), KernelError> {
646        Err(KernelError::PermissionDenied {
647            operation: "truncate PTY slave",
648        })
649    }
650}
651
652#[cfg(test)]
653mod tests {
654    use super::*;
655
656    #[test]
657    fn test_pty_creation() {
658        let mut manager = PtyManager::new();
659        let result = manager.create_pty();
660        assert!(result.is_ok());
661    }
662
663    #[test]
664    fn test_pty_read_write() {
665        let master = PtyMaster::new(0);
666        let slave = PtySlave::new(0, 0);
667
668        // Write from master
669        let data = b"Hello PTY!";
670        assert!(master.write(data).is_ok());
671
672        // Read would require accessing the manager
673        // This is a simplified test
674    }
675
676    #[test]
677    fn test_winsize() {
678        let master = PtyMaster::new(0);
679        let winsize = Winsize {
680            rows: 30,
681            cols: 100,
682            xpixel: 800,
683            ypixel: 600,
684        };
685
686        master.set_winsize(winsize);
687        let retrieved = master.get_winsize();
688
689        assert_eq!(retrieved.rows, 30);
690        assert_eq!(retrieved.cols, 100);
691    }
692}