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

veridian_kernel/pkg/ports/
collection.rs

1//! Port Collection Management
2//!
3//! Organises ports into categories (e.g., core, devel, libs, net,
4//! security, utils) and provides search and synchronisation capabilities.
5//! A `PortCollection` is the top-level directory of available ports,
6//! analogous to the ports tree in BSD-like operating systems.
7
8#[cfg(feature = "alloc")]
9use alloc::{collections::BTreeMap, string::String, vec::Vec};
10
11#[cfg(feature = "alloc")]
12use crate::error::KernelError;
13
14/// Standard port categories shipped with VeridianOS.
15#[cfg(feature = "alloc")]
16pub const STANDARD_CATEGORIES: &[&str] = &[
17    "core",     // Essential system packages
18    "devel",    // Development tools and libraries
19    "libs",     // Shared / static libraries
20    "net",      // Networking utilities and daemons
21    "security", // Security tools and cryptographic software
22    "utils",    // General-purpose utilities
23];
24
25/// A categorised collection of available ports.
26///
27/// Each category maps to a list of port names that belong to it. The
28/// collection is typically populated by scanning `/usr/ports/` or by
29/// syncing with a remote ports index.
30#[cfg(feature = "alloc")]
31pub struct PortCollection {
32    /// Category -> list of port names
33    categories: BTreeMap<String, Vec<String>>,
34    /// Last sync timestamp (seconds since boot / epoch)
35    last_sync: u64,
36}
37
38#[cfg(feature = "alloc")]
39impl PortCollection {
40    /// Create a new, empty collection with the standard categories
41    /// pre-registered.
42    pub fn new() -> Self {
43        let mut categories = BTreeMap::new();
44        for &cat in STANDARD_CATEGORIES {
45            categories.insert(String::from(cat), Vec::new());
46        }
47        Self {
48            categories,
49            last_sync: 0,
50        }
51    }
52
53    /// Add a port to a category. If the category does not exist it is
54    /// created automatically.
55    pub fn add_port(&mut self, category: &str, port_name: &str) {
56        let list = self
57            .categories
58            .entry(String::from(category))
59            .or_insert_with(Vec::new);
60
61        // Avoid duplicates
62        if !list.iter().any(|n| n == port_name) {
63            list.push(String::from(port_name));
64        }
65    }
66
67    /// Remove a port from a category. Returns `true` if the port was found
68    /// and removed, `false` otherwise.
69    pub fn remove_port(&mut self, category: &str, port_name: &str) -> bool {
70        if let Some(list) = self.categories.get_mut(category) {
71            if let Some(pos) = list.iter().position(|n| n == port_name) {
72                list.remove(pos);
73                return true;
74            }
75        }
76        false
77    }
78
79    /// List all known category names (sorted).
80    pub fn list_categories(&self) -> Vec<&str> {
81        self.categories.keys().map(|s| s.as_str()).collect()
82    }
83
84    /// List port names within a specific category.
85    pub fn list_ports_in_category(&self, category: &str) -> Option<&[String]> {
86        self.categories.get(category).map(|v| v.as_slice())
87    }
88
89    /// Search all categories for ports whose name contains `query`
90    /// (case-insensitive). Returns `(category, port_name)` pairs.
91    pub fn search_ports(&self, query: &str) -> Vec<(String, String)> {
92        let query_lower = query.to_lowercase();
93        let mut results = Vec::new();
94
95        for (category, ports) in &self.categories {
96            for port_name in ports {
97                if port_name.to_lowercase().contains(&query_lower) {
98                    results.push((category.clone(), port_name.clone()));
99                }
100            }
101        }
102
103        results
104    }
105
106    /// Return the total number of ports across all categories.
107    pub fn total_ports(&self) -> usize {
108        self.categories.values().map(|v| v.len()).sum()
109    }
110
111    /// Return the number of categories.
112    pub fn category_count(&self) -> usize {
113        self.categories.len()
114    }
115
116    /// Synchronise the collection from the ports tree on disk.
117    ///
118    /// First attempts to scan `/usr/ports/` via the VFS to discover real
119    /// ports. If the VFS is not available or no ports are found, falls back
120    /// to a set of well-known demo ports so the framework is exercisable
121    /// even without a real filesystem.
122    ///
123    /// Returns the number of ports discovered.
124    pub fn sync_collection(&mut self) -> Result<usize, KernelError> {
125        crate::println!("[PORTS] Syncing port collection from /usr/ports/ ...");
126
127        // Try scanning the VFS first
128        let vfs_count = self.scan_ports_directory();
129
130        if vfs_count > 0 {
131            // VFS scan found ports -- use those
132            self.last_sync = crate::arch::timer::get_timestamp_secs();
133
134            crate::println!(
135                "[PORTS] Sync complete (VFS): {} ports in {} categories",
136                self.total_ports(),
137                self.category_count()
138            );
139
140            return Ok(vfs_count);
141        }
142
143        // Fallback: register a set of well-known ports for demonstration
144        // so the framework is exercisable even without a real filesystem.
145        crate::println!("[PORTS] No VFS ports found, loading demo ports");
146
147        let demo_ports: &[(&str, &str)] = &[
148            ("core", "coreutils"),
149            ("core", "bash"),
150            ("core", "grep"),
151            ("core", "sed"),
152            ("devel", "gcc"),
153            ("devel", "make"),
154            ("devel", "cmake"),
155            ("devel", "git"),
156            ("libs", "openssl"),
157            ("libs", "zlib"),
158            ("libs", "libpng"),
159            ("net", "curl"),
160            ("net", "wget"),
161            ("net", "openssh"),
162            ("security", "gnupg"),
163            ("security", "nmap"),
164            ("utils", "vim"),
165            ("utils", "tmux"),
166            ("utils", "htop"),
167        ];
168
169        let mut count = 0;
170        for &(category, name) in demo_ports {
171            self.add_port(category, name);
172            count += 1;
173        }
174
175        self.last_sync = crate::arch::timer::get_timestamp_secs();
176
177        crate::println!(
178            "[PORTS] Sync complete (demo): {} ports in {} categories",
179            self.total_ports(),
180            self.category_count()
181        );
182
183        Ok(count)
184    }
185
186    /// Scan the `/usr/ports/` directory tree via VFS to discover ports.
187    ///
188    /// Expects the layout `/usr/ports/<category>/<port_name>/`. Each
189    /// sub-directory under a category is registered as a port in that
190    /// category. Returns the number of ports discovered (0 if VFS is
191    /// unavailable or the directory does not exist).
192    #[cfg_attr(not(target_arch = "x86_64"), allow(unused_variables))]
193    fn scan_ports_directory(&mut self) -> usize {
194        let vfs_lock = match crate::fs::try_get_vfs() {
195            Some(lock) => lock,
196            None => {
197                crate::println!("[PORTS] VFS not available, skipping directory scan");
198                return 0;
199            }
200        };
201
202        let vfs = vfs_lock.read();
203        let ports_root = match vfs.resolve_path("/usr/ports") {
204            Ok(node) => node,
205            Err(_) => {
206                crate::println!("[PORTS] /usr/ports not found in VFS");
207                return 0;
208            }
209        };
210
211        // List category directories
212        let category_entries = match ports_root.readdir() {
213            Ok(entries) => entries,
214            Err(_) => {
215                crate::println!("[PORTS] Cannot list /usr/ports directory");
216                return 0;
217            }
218        };
219
220        let mut total = 0;
221
222        for cat_entry in &category_entries {
223            // Only process directories (categories)
224            if cat_entry.node_type != crate::fs::NodeType::Directory {
225                continue;
226            }
227
228            let category = &cat_entry.name;
229            let cat_path = alloc::format!("/usr/ports/{}", category);
230
231            // List port directories within this category
232            let cat_node = match vfs.resolve_path(&cat_path) {
233                Ok(n) => n,
234                Err(_) => continue,
235            };
236
237            let port_entries = match cat_node.readdir() {
238                Ok(entries) => entries,
239                Err(_) => continue,
240            };
241
242            for port_entry in &port_entries {
243                if port_entry.node_type != crate::fs::NodeType::Directory {
244                    continue;
245                }
246
247                self.add_port(category, &port_entry.name);
248                total += 1;
249            }
250        }
251
252        if total > 0 {
253            crate::println!(
254                "[PORTS] VFS scan discovered {} ports under /usr/ports/",
255                total
256            );
257        }
258
259        total
260    }
261
262    /// Return the timestamp (seconds) of the last successful sync.
263    pub fn last_sync_time(&self) -> u64 {
264        self.last_sync
265    }
266
267    /// Check whether the collection has been synced at least once.
268    pub fn is_synced(&self) -> bool {
269        self.last_sync > 0 || self.total_ports() > 0
270    }
271}
272
273#[cfg(feature = "alloc")]
274impl Default for PortCollection {
275    fn default() -> Self {
276        Self::new()
277    }
278}