inode.rs
1 //! Inode mapping for the FUSE filesystem. 2 //! 3 //! Maps u64 inodes to accessibility tree objects and virtual files. 4 5 use std::{ 6 collections::HashMap, 7 sync::atomic::{AtomicU64, Ordering}, 8 }; 9 10 use atspi::object_ref::ObjectRefOwned; 11 use strum::{Display, EnumIter, EnumString, IntoStaticStr}; 12 13 /// Reserved inode for the root directory. 14 pub const ROOT_INODE: u64 = 1; 15 16 /// Reserved inode for the README.md file. 17 pub const README_INODE: u64 = 2; 18 19 /// Reserved inode for the .gitignore file. 20 pub const GITIGNORE_INODE: u64 = 3; 21 22 /// First available inode for dynamic allocation. 23 const FIRST_DYNAMIC_INODE: u64 = 4; 24 25 /// What an inode represents in the filesystem. 26 #[derive(Debug, Clone)] 27 pub enum InodeTarget { 28 /// Root directory (lists all applications). 29 Root, 30 /// README.md file at the root. 31 Readme, 32 /// .gitignore file at the root. 33 Gitignore, 34 /// An accessible object (directory). 35 Object { 36 /// The object reference. 37 obj_ref: ObjectRefOwned, 38 /// Parent directory inode (`ROOT_INODE` for top-level apps). 39 parent_inode: u64, 40 /// Reference name for this object (e.g., "firefox-1.74", "frame-42"). 41 reference: String, 42 }, 43 /// A property file for an object. 44 Property(ObjectRefOwned, PropertyKind), 45 /// An interface-specific property file for an object. 46 InterfaceProperty(ObjectRefOwned, InterfacePropertyKind), 47 /// Events symlink (points to real FIFO in tempdir). 48 Events { 49 /// The object reference (used for watch registration). 50 obj_ref: ObjectRefOwned, 51 /// Reference name (used for event filtering). 52 reference: String, 53 }, 54 } 55 56 /// Property files available for each accessible object. 57 #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, EnumIter, Display, EnumString, IntoStaticStr)] 58 pub enum PropertyKind { 59 #[strum(serialize = "name")] 60 Name, 61 #[strum(serialize = "role")] 62 Role, 63 #[strum(serialize = "description")] 64 Description, 65 #[strum(serialize = "states.json")] 66 States, 67 #[strum(serialize = "interfaces.json")] 68 Interfaces, 69 #[strum(serialize = "attributes.json")] 70 Attributes, 71 #[strum(serialize = "relations.json")] 72 Relations, 73 #[strum(serialize = "child_count")] 74 ChildCount, 75 #[strum(serialize = "accessible_id")] 76 AccessibleId, 77 #[strum(serialize = "locale")] 78 Locale, 79 #[strum(serialize = "help_text")] 80 HelpText, 81 #[strum(serialize = "index_in_parent")] 82 IndexInParent, 83 #[strum(serialize = "parent")] 84 Parent, 85 #[strum(serialize = "role_name")] 86 RoleName, 87 #[strum(serialize = "localized_role_name")] 88 LocalizedRoleName, 89 #[strum(serialize = "application")] 90 Application, 91 } 92 93 /// Interface-specific property files shown only when the interface is present. 94 #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, EnumIter, Display, EnumString, IntoStaticStr)] 95 pub enum InterfacePropertyKind { 96 // Text interface 97 #[strum(serialize = "text")] 98 Text, 99 #[strum(serialize = "character_count")] 100 CharacterCount, 101 #[strum(serialize = "caret_offset")] 102 CaretOffset, 103 // Action interface 104 #[strum(serialize = "actions.json")] 105 Actions, 106 // Value interface 107 #[strum(serialize = "value.json")] 108 Value, 109 // Component interface 110 #[strum(serialize = "bounds.json")] 111 Bounds, 112 // Image interface 113 #[strum(serialize = "image.json")] 114 Image, 115 // Table interface 116 #[strum(serialize = "table.json")] 117 Table, 118 // TableCell interface 119 #[strum(serialize = "cell.json")] 120 Cell, 121 // Document interface 122 #[strum(serialize = "document.json")] 123 Document, 124 // Selection interface 125 #[strum(serialize = "selection.json")] 126 Selection, 127 // Application interface 128 #[strum(serialize = "application.json")] 129 Application, 130 // Hypertext interface 131 #[strum(serialize = "links.json")] 132 Links, 133 // Collection interface 134 #[strum(serialize = "collection.json")] 135 Collection, 136 // Hyperlink interface 137 #[strum(serialize = "hyperlink.json")] 138 Hyperlink, 139 // Text interface - selections 140 #[strum(serialize = "text_selections.json")] 141 TextSelections, 142 // Text interface - default attributes 143 #[strum(serialize = "text_attributes.json")] 144 TextAttributes, 145 } 146 147 impl InterfacePropertyKind { 148 /// Which AT-SPI interface this property requires. 149 pub const fn interface(self) -> &'static str { 150 match self { 151 Self::Text 152 | Self::CharacterCount 153 | Self::CaretOffset 154 | Self::TextSelections 155 | Self::TextAttributes => "Text", 156 Self::Actions => "Action", 157 Self::Value => "Value", 158 Self::Bounds => "Component", 159 Self::Image => "Image", 160 Self::Table => "Table", 161 Self::Cell => "TableCell", 162 Self::Document => "Document", 163 Self::Selection => "Selection", 164 Self::Application => "Application", 165 Self::Links => "Hypertext", 166 Self::Collection => "Collection", 167 Self::Hyperlink => "Hyperlink", 168 } 169 } 170 } 171 172 /// Bidirectional mapping between inodes and their targets. 173 pub struct InodeMap { 174 /// Counter for allocating new inodes. 175 next_inode: AtomicU64, 176 /// Map from inode to target. 177 inode_to_target: HashMap<u64, InodeTarget>, 178 /// Map from object reference to its directory inode. 179 object_to_inode: HashMap<ObjectRefOwned, u64>, 180 /// Map from (object reference, property kind) to property file inode. 181 property_to_inode: HashMap<(ObjectRefOwned, PropertyKind), u64>, 182 /// Map from (object reference, interface property kind) to property file inode. 183 interface_property_to_inode: HashMap<(ObjectRefOwned, InterfacePropertyKind), u64>, 184 /// Map from object reference to its events.json inode. 185 events_to_inode: HashMap<ObjectRefOwned, u64>, 186 } 187 188 /// Thread-safe wrapper around [`InodeMap`]. 189 pub type SharedInodeMap = std::sync::Arc<std::sync::Mutex<InodeMap>>; 190 191 impl InodeMap { 192 /// Create a new inode map with reserved entries. 193 pub fn new() -> Self { 194 let mut map = Self { 195 next_inode: AtomicU64::new(FIRST_DYNAMIC_INODE), 196 inode_to_target: HashMap::new(), 197 object_to_inode: HashMap::new(), 198 property_to_inode: HashMap::new(), 199 interface_property_to_inode: HashMap::new(), 200 events_to_inode: HashMap::new(), 201 }; 202 203 // Add reserved inodes 204 map.inode_to_target.insert(ROOT_INODE, InodeTarget::Root); 205 map.inode_to_target 206 .insert(README_INODE, InodeTarget::Readme); 207 map.inode_to_target 208 .insert(GITIGNORE_INODE, InodeTarget::Gitignore); 209 210 map 211 } 212 213 /// Create a new shared inode map. 214 pub fn new_shared() -> SharedInodeMap { 215 std::sync::Arc::new(std::sync::Mutex::new(Self::new())) 216 } 217 218 /// Allocate a new inode. 219 fn alloc_inode(&self) -> u64 { 220 self.next_inode.fetch_add(1, Ordering::Relaxed) 221 } 222 223 /// Get or create an inode for an accessible object directory. 224 /// 225 /// The `parent_inode` is stored with the object so `..` can point to the correct parent. 226 /// For top-level applications, pass `ROOT_INODE`. 227 pub fn get_or_create_object_inode( 228 &mut self, 229 obj_ref: &ObjectRefOwned, 230 parent_inode: u64, 231 reference: String, 232 ) -> u64 { 233 if let Some(&inode) = self.object_to_inode.get(obj_ref) { 234 return inode; 235 } 236 237 let inode = self.alloc_inode(); 238 self.inode_to_target.insert( 239 inode, 240 InodeTarget::Object { 241 obj_ref: obj_ref.clone(), 242 parent_inode, 243 reference, 244 }, 245 ); 246 self.object_to_inode.insert(obj_ref.clone(), inode); 247 inode 248 } 249 250 /// Get or create an inode for a property file. 251 pub fn get_or_create_property_inode( 252 &mut self, 253 obj_ref: &ObjectRefOwned, 254 kind: PropertyKind, 255 ) -> u64 { 256 let key = (obj_ref.clone(), kind); 257 if let Some(&inode) = self.property_to_inode.get(&key) { 258 return inode; 259 } 260 261 let inode = self.alloc_inode(); 262 self.inode_to_target 263 .insert(inode, InodeTarget::Property(obj_ref.clone(), kind)); 264 self.property_to_inode.insert(key, inode); 265 inode 266 } 267 268 /// Get or create an inode for an interface-specific property file. 269 pub fn get_or_create_interface_property_inode( 270 &mut self, 271 obj_ref: &ObjectRefOwned, 272 kind: InterfacePropertyKind, 273 ) -> u64 { 274 let key = (obj_ref.clone(), kind); 275 if let Some(&inode) = self.interface_property_to_inode.get(&key) { 276 return inode; 277 } 278 279 let inode = self.alloc_inode(); 280 self.inode_to_target 281 .insert(inode, InodeTarget::InterfaceProperty(obj_ref.clone(), kind)); 282 self.interface_property_to_inode.insert(key, inode); 283 inode 284 } 285 286 /// Get or create an inode for an events symlink. 287 pub fn get_or_create_events_inode( 288 &mut self, 289 obj_ref: &ObjectRefOwned, 290 reference: String, 291 ) -> u64 { 292 if let Some(&inode) = self.events_to_inode.get(obj_ref) { 293 return inode; 294 } 295 296 let inode = self.alloc_inode(); 297 self.inode_to_target.insert( 298 inode, 299 InodeTarget::Events { 300 obj_ref: obj_ref.clone(), 301 reference, 302 }, 303 ); 304 self.events_to_inode.insert(obj_ref.clone(), inode); 305 inode 306 } 307 308 /// Look up what an inode represents. 309 pub fn get(&self, inode: u64) -> Option<&InodeTarget> { 310 self.inode_to_target.get(&inode) 311 } 312 313 /// Remove all inodes associated with an object (directory, properties, events). 314 /// 315 /// Call this when an object becomes defunct to free up inode entries. 316 pub fn remove_object(&mut self, obj_ref: &ObjectRefOwned) { 317 use strum::IntoEnumIterator; 318 319 // Remove the object directory inode 320 if let Some(inode) = self.object_to_inode.remove(obj_ref) { 321 self.inode_to_target.remove(&inode); 322 } 323 324 // Remove all property inodes for this object 325 for kind in PropertyKind::iter() { 326 let key = (obj_ref.clone(), kind); 327 if let Some(inode) = self.property_to_inode.remove(&key) { 328 self.inode_to_target.remove(&inode); 329 } 330 } 331 332 // Remove all interface property inodes for this object 333 for kind in InterfacePropertyKind::iter() { 334 let key = (obj_ref.clone(), kind); 335 if let Some(inode) = self.interface_property_to_inode.remove(&key) { 336 self.inode_to_target.remove(&inode); 337 } 338 } 339 340 // Remove the events inode 341 if let Some(inode) = self.events_to_inode.remove(obj_ref) { 342 self.inode_to_target.remove(&inode); 343 } 344 } 345 346 /// Remove all inodes associated with a bus name. 347 /// 348 /// Call this when a peer disconnects from the D-Bus to clean up all objects 349 /// that belonged to that peer. Returns the number of objects removed. 350 pub fn remove_objects_by_bus_name(&mut self, bus_name: &str) -> usize { 351 // Collect all object refs that match this bus name 352 let to_remove: Vec<ObjectRefOwned> = self 353 .object_to_inode 354 .keys() 355 .filter(|obj_ref| obj_ref.name_as_str() == Some(bus_name)) 356 .cloned() 357 .collect(); 358 359 let count = to_remove.len(); 360 for obj_ref in to_remove { 361 self.remove_object(&obj_ref); 362 } 363 count 364 } 365 } 366 367 impl Default for InodeMap { 368 fn default() -> Self { 369 Self::new() 370 } 371 }