|
| 1 | +//! DevPTS Filesystem (devpts) |
| 2 | +//! |
| 3 | +//! Provides a virtual filesystem mounted at /dev/pts containing PTY slave devices. |
| 4 | +//! Unlike ext2, devptsfs doesn't use disk storage - all nodes are virtual and |
| 5 | +//! dynamically generated based on active PTY pairs. |
| 6 | +//! |
| 7 | +//! # Supported Entries |
| 8 | +//! |
| 9 | +//! - `/dev/pts/` - Directory containing active PTY slave devices |
| 10 | +//! - `/dev/pts/0` - First PTY slave (if allocated and unlocked) |
| 11 | +//! - `/dev/pts/1` - Second PTY slave, etc. |
| 12 | +//! |
| 13 | +//! # Architecture |
| 14 | +//! |
| 15 | +//! ```text |
| 16 | +//! sys_open("/dev/pts/0") |
| 17 | +//! | |
| 18 | +//! v |
| 19 | +//! devpts_lookup("0") |
| 20 | +//! | |
| 21 | +//! v |
| 22 | +//! Check PTY 0 exists and is unlocked |
| 23 | +//! | |
| 24 | +//! v |
| 25 | +//! Return PtySlave file descriptor |
| 26 | +//! ``` |
| 27 | +
|
| 28 | +use alloc::string::String; |
| 29 | +use alloc::vec::Vec; |
| 30 | +use spin::Mutex; |
| 31 | + |
| 32 | +use crate::tty::pty; |
| 33 | + |
| 34 | +/// Directory entry for /dev/pts listing |
| 35 | +#[derive(Debug, Clone)] |
| 36 | +pub struct PtsEntry { |
| 37 | + /// PTY number (0, 1, 2, ...) |
| 38 | + pub pty_num: u32, |
| 39 | + /// Inode number for stat |
| 40 | + pub inode: u64, |
| 41 | +} |
| 42 | + |
| 43 | +impl PtsEntry { |
| 44 | + /// Get the entry name (just the number as string) |
| 45 | + pub fn name(&self) -> String { |
| 46 | + alloc::format!("{}", self.pty_num) |
| 47 | + } |
| 48 | +} |
| 49 | + |
| 50 | +/// Global devpts state |
| 51 | +struct DevptsState { |
| 52 | + /// Whether devpts is initialized |
| 53 | + initialized: bool, |
| 54 | +} |
| 55 | + |
| 56 | +impl DevptsState { |
| 57 | + const fn new() -> Self { |
| 58 | + Self { |
| 59 | + initialized: false, |
| 60 | + } |
| 61 | + } |
| 62 | +} |
| 63 | + |
| 64 | +static DEVPTS: Mutex<DevptsState> = Mutex::new(DevptsState::new()); |
| 65 | + |
| 66 | +/// Initialize devpts filesystem |
| 67 | +pub fn init() { |
| 68 | + let mut devpts = DEVPTS.lock(); |
| 69 | + if devpts.initialized { |
| 70 | + log::warn!("devpts already initialized"); |
| 71 | + return; |
| 72 | + } |
| 73 | + |
| 74 | + devpts.initialized = true; |
| 75 | + log::info!("devpts: initialized at /dev/pts"); |
| 76 | + |
| 77 | + // Register mount point |
| 78 | + crate::fs::vfs::mount::mount("/dev/pts", "devpts"); |
| 79 | +} |
| 80 | + |
| 81 | +/// Look up a PTY slave device by name (the number as string, e.g., "0", "1") |
| 82 | +/// |
| 83 | +/// Returns the PTY number if: |
| 84 | +/// 1. The name is a valid number |
| 85 | +/// 2. A PTY with that number exists |
| 86 | +/// 3. The PTY is unlocked (unlockpt was called) |
| 87 | +/// |
| 88 | +/// # Arguments |
| 89 | +/// * `name` - The PTY number as a string (without /dev/pts/ prefix) |
| 90 | +/// |
| 91 | +/// # Returns |
| 92 | +/// * `Some(pty_num)` if the PTY exists and is unlocked |
| 93 | +/// * `None` if the PTY doesn't exist or is still locked |
| 94 | +pub fn lookup(name: &str) -> Option<u32> { |
| 95 | + // Parse the PTY number from name |
| 96 | + let pty_num: u32 = name.parse().ok()?; |
| 97 | + |
| 98 | + // Check if PTY exists and is unlocked |
| 99 | + let pair = pty::get(pty_num)?; |
| 100 | + if !pair.is_unlocked() { |
| 101 | + return None; // PTY exists but hasn't been unlocked yet |
| 102 | + } |
| 103 | + |
| 104 | + Some(pty_num) |
| 105 | +} |
| 106 | + |
| 107 | +/// Look up a PTY slave by inode number |
| 108 | +/// |
| 109 | +/// For devpts, the inode number is derived from the PTY number. |
| 110 | +/// We use a base offset to avoid collision with other filesystem inodes. |
| 111 | +pub fn lookup_by_inode(inode: u64) -> Option<u32> { |
| 112 | + // Inode = PTY_INODE_BASE + pty_num |
| 113 | + const PTY_INODE_BASE: u64 = 0x10000; |
| 114 | + |
| 115 | + if inode < PTY_INODE_BASE { |
| 116 | + return None; |
| 117 | + } |
| 118 | + |
| 119 | + let pty_num = (inode - PTY_INODE_BASE) as u32; |
| 120 | + |
| 121 | + // Verify the PTY exists and is unlocked |
| 122 | + let pair = pty::get(pty_num)?; |
| 123 | + if !pair.is_unlocked() { |
| 124 | + return None; |
| 125 | + } |
| 126 | + |
| 127 | + Some(pty_num) |
| 128 | +} |
| 129 | + |
| 130 | +/// Get inode number for a PTY slave |
| 131 | +pub fn get_inode(pty_num: u32) -> u64 { |
| 132 | + const PTY_INODE_BASE: u64 = 0x10000; |
| 133 | + PTY_INODE_BASE + pty_num as u64 |
| 134 | +} |
| 135 | + |
| 136 | +/// List all active and unlocked PTY slave entries |
| 137 | +/// |
| 138 | +/// Returns entries for all PTYs that: |
| 139 | +/// 1. Have been allocated |
| 140 | +/// 2. Have been unlocked (unlockpt called) |
| 141 | +pub fn list_entries() -> Vec<PtsEntry> { |
| 142 | + pty::list_active() |
| 143 | + .into_iter() |
| 144 | + .filter_map(|pty_num| { |
| 145 | + // Only include unlocked PTYs |
| 146 | + let pair = pty::get(pty_num)?; |
| 147 | + if !pair.is_unlocked() { |
| 148 | + return None; |
| 149 | + } |
| 150 | + Some(PtsEntry { |
| 151 | + pty_num, |
| 152 | + inode: get_inode(pty_num), |
| 153 | + }) |
| 154 | + }) |
| 155 | + .collect() |
| 156 | +} |
| 157 | + |
| 158 | +/// List all PTY slave names (for directory listing) |
| 159 | +pub fn list_names() -> Vec<String> { |
| 160 | + list_entries().into_iter().map(|e| e.name()).collect() |
| 161 | +} |
| 162 | + |
| 163 | +/// Check if devpts is initialized |
| 164 | +pub fn is_initialized() -> bool { |
| 165 | + DEVPTS.lock().initialized |
| 166 | +} |
| 167 | + |
| 168 | +/// Get device numbers for a PTY slave |
| 169 | +/// |
| 170 | +/// PTY slaves use major number 136 (standard Unix/Linux convention) |
| 171 | +/// and minor number = PTY number |
| 172 | +pub fn get_device_numbers(pty_num: u32) -> (u32, u32) { |
| 173 | + const PTY_SLAVE_MAJOR: u32 = 136; |
| 174 | + (PTY_SLAVE_MAJOR, pty_num) |
| 175 | +} |
| 176 | + |
| 177 | +/// Get the combined device number (major << 8 | minor) for stat st_rdev |
| 178 | +pub fn get_rdev(pty_num: u32) -> u64 { |
| 179 | + let (major, minor) = get_device_numbers(pty_num); |
| 180 | + ((major as u64) << 8) | (minor as u64) |
| 181 | +} |
| 182 | + |
| 183 | +#[cfg(test)] |
| 184 | +mod tests { |
| 185 | + use super::*; |
| 186 | + |
| 187 | + #[test] |
| 188 | + fn test_get_inode() { |
| 189 | + assert_eq!(get_inode(0), 0x10000); |
| 190 | + assert_eq!(get_inode(1), 0x10001); |
| 191 | + assert_eq!(get_inode(255), 0x100ff); |
| 192 | + } |
| 193 | + |
| 194 | + #[test] |
| 195 | + fn test_get_device_numbers() { |
| 196 | + let (major, minor) = get_device_numbers(0); |
| 197 | + assert_eq!(major, 136); |
| 198 | + assert_eq!(minor, 0); |
| 199 | + |
| 200 | + let (major, minor) = get_device_numbers(5); |
| 201 | + assert_eq!(major, 136); |
| 202 | + assert_eq!(minor, 5); |
| 203 | + } |
| 204 | + |
| 205 | + #[test] |
| 206 | + fn test_get_rdev() { |
| 207 | + // major=136 (0x88), minor=0: rdev = 0x8800 |
| 208 | + assert_eq!(get_rdev(0), 0x8800); |
| 209 | + // major=136 (0x88), minor=5: rdev = 0x8805 |
| 210 | + assert_eq!(get_rdev(5), 0x8805); |
| 211 | + } |
| 212 | +} |
0 commit comments