[<prev] [next>] [<thread-prev] [thread-next>] [day] [month] [year] [list]
Message-Id: <20250624180742.5795-23-sashal@kernel.org>
Date: Tue, 24 Jun 2025 14:07:42 -0400
From: Sasha Levin <sashal@...nel.org>
To: linux-kernel@...r.kernel.org
Cc: linux-doc@...r.kernel.org,
linux-api@...r.kernel.org,
workflows@...r.kernel.org,
tools@...nel.org,
Sasha Levin <sashal@...nel.org>
Subject: [RFC v2 22/22] tools/kapi: Add kernel API specification extraction tool
The kapi tool extracts and displays kernel API specifications.
Signed-off-by: Sasha Levin <sashal@...nel.org>
---
Documentation/admin-guide/kernel-api-spec.rst | 198 +-
tools/kapi/.gitignore | 4 +
tools/kapi/Cargo.toml | 19 +
tools/kapi/src/extractor/debugfs.rs | 415 +++++
tools/kapi/src/extractor/mod.rs | 411 +++++
tools/kapi/src/extractor/source_parser.rs | 1625 +++++++++++++++++
.../src/extractor/vmlinux/binary_utils.rs | 283 +++
tools/kapi/src/extractor/vmlinux/mod.rs | 989 ++++++++++
tools/kapi/src/formatter/json.rs | 420 +++++
tools/kapi/src/formatter/mod.rs | 130 ++
tools/kapi/src/formatter/plain.rs | 465 +++++
tools/kapi/src/formatter/rst.rs | 468 +++++
tools/kapi/src/formatter/shall.rs | 605 ++++++
tools/kapi/src/main.rs | 130 ++
14 files changed, 6159 insertions(+), 3 deletions(-)
create mode 100644 tools/kapi/.gitignore
create mode 100644 tools/kapi/Cargo.toml
create mode 100644 tools/kapi/src/extractor/debugfs.rs
create mode 100644 tools/kapi/src/extractor/mod.rs
create mode 100644 tools/kapi/src/extractor/source_parser.rs
create mode 100644 tools/kapi/src/extractor/vmlinux/binary_utils.rs
create mode 100644 tools/kapi/src/extractor/vmlinux/mod.rs
create mode 100644 tools/kapi/src/formatter/json.rs
create mode 100644 tools/kapi/src/formatter/mod.rs
create mode 100644 tools/kapi/src/formatter/plain.rs
create mode 100644 tools/kapi/src/formatter/rst.rs
create mode 100644 tools/kapi/src/formatter/shall.rs
create mode 100644 tools/kapi/src/main.rs
diff --git a/Documentation/admin-guide/kernel-api-spec.rst b/Documentation/admin-guide/kernel-api-spec.rst
index 3a63f6711e27b..9b452753111ad 100644
--- a/Documentation/admin-guide/kernel-api-spec.rst
+++ b/Documentation/admin-guide/kernel-api-spec.rst
@@ -31,7 +31,9 @@ The framework aims to:
common programming errors during development and testing.
3. **Support Tooling**: Export API specifications in machine-readable formats for
- use by static analyzers, documentation generators, and development tools.
+ use by static analyzers, documentation generators, and development tools. The
+ ``kapi`` tool (see `The kapi Tool`_) provides comprehensive extraction and
+ formatting capabilities.
4. **Enhance Debugging**: Provide detailed API information at runtime through debugfs
for debugging and introspection.
@@ -71,6 +73,13 @@ The framework consists of several key components:
- Type-safe parameter specifications
- Context and constraint definitions
+5. **kapi Tool** (``tools/kapi/``)
+
+ - Userspace utility for extracting specifications
+ - Multiple input sources (source, binary, debugfs)
+ - Multiple output formats (plain, JSON, RST)
+ - Testing and validation utilities
+
Data Model
----------
@@ -344,8 +353,177 @@ Documentation Generation
------------------------
The framework exports specifications via debugfs that can be used
-to generate documentation. Tools for automatic documentation generation
-from specifications are planned for future development.
+to generate documentation. The ``kapi`` tool provides comprehensive
+extraction and formatting capabilities for kernel API specifications.
+
+The kapi Tool
+=============
+
+Overview
+--------
+
+The ``kapi`` tool is a userspace utility that extracts and displays kernel API
+specifications from multiple sources. It provides a unified interface to access
+API documentation whether from compiled kernels, source code, or runtime systems.
+
+Installation
+------------
+
+Build the tool from the kernel source tree::
+
+ $ cd tools/kapi
+ $ cargo build --release
+
+ # Optional: Install system-wide
+ $ cargo install --path .
+
+The tool requires Rust and Cargo to build. The binary will be available at
+``tools/kapi/target/release/kapi``.
+
+Command-Line Usage
+------------------
+
+Basic syntax::
+
+ kapi [OPTIONS] [API_NAME]
+
+Options:
+
+- ``--vmlinux <PATH>``: Extract from compiled kernel binary
+- ``--source <PATH>``: Extract from kernel source code
+- ``--debugfs <PATH>``: Extract from debugfs (default: /sys/kernel/debug)
+- ``-f, --format <FORMAT>``: Output format (plain, json, rst)
+- ``-h, --help``: Display help information
+- ``-V, --version``: Display version information
+
+Input Modes
+-----------
+
+**1. Source Code Mode**
+
+Extract specifications directly from kernel source::
+
+ # Scan entire kernel source tree
+ $ kapi --source /path/to/linux
+
+ # Extract from specific file
+ $ kapi --source kernel/sched/core.c
+
+ # Get details for specific API
+ $ kapi --source /path/to/linux sys_sched_yield
+
+**2. Vmlinux Mode**
+
+Extract from compiled kernel with debug symbols::
+
+ # List all APIs in vmlinux
+ $ kapi --vmlinux /boot/vmlinux-5.15.0
+
+ # Get specific syscall details
+ $ kapi --vmlinux ./vmlinux sys_read
+
+**3. Debugfs Mode**
+
+Extract from running kernel via debugfs::
+
+ # Use default debugfs path
+ $ kapi
+
+ # Use custom debugfs mount
+ $ kapi --debugfs /mnt/debugfs
+
+ # Get specific API from running kernel
+ $ kapi sys_write
+
+Output Formats
+--------------
+
+**Plain Text Format** (default)::
+
+ $ kapi sys_read
+
+ Detailed information for sys_read:
+ ==================================
+ Description: Read from a file descriptor
+
+ Detailed Description:
+ Reads up to count bytes from file descriptor fd into the buffer starting at buf.
+
+ Execution Context:
+ - KAPI_CTX_PROCESS | KAPI_CTX_SLEEPABLE
+
+ Parameters (3):
+
+ Available since: 1.0
+
+**JSON Format**::
+
+ $ kapi --format json sys_read
+ {
+ "api_details": {
+ "name": "sys_read",
+ "description": "Read from a file descriptor",
+ "long_description": "Reads up to count bytes...",
+ "context_flags": ["KAPI_CTX_PROCESS | KAPI_CTX_SLEEPABLE"],
+ "since_version": "1.0"
+ }
+ }
+
+**ReStructuredText Format**::
+
+ $ kapi --format rst sys_read
+
+ sys_read
+ ========
+
+ **Read from a file descriptor**
+
+ Reads up to count bytes from file descriptor fd into the buffer...
+
+Usage Examples
+--------------
+
+**Generate complete API documentation**::
+
+ # Export all kernel APIs to JSON
+ $ kapi --source /path/to/linux --format json > kernel-apis.json
+
+ # Generate RST documentation for all syscalls
+ $ kapi --vmlinux ./vmlinux --format rst > syscalls.rst
+
+ # List APIs from specific subsystem
+ $ kapi --source drivers/gpu/drm/
+
+**Integration with other tools**::
+
+ # Find all APIs that can sleep
+ $ kapi --format json | jq '.apis[] | select(.context_flags[] | contains("SLEEPABLE"))'
+
+ # Generate markdown documentation
+ $ kapi --format rst sys_mmap | pandoc -f rst -t markdown
+
+**Debugging and analysis**::
+
+ # Compare API between kernel versions
+ $ diff <(kapi --vmlinux vmlinux-5.10) <(kapi --vmlinux vmlinux-5.15)
+
+ # Check if specific API exists
+ $ kapi --source . my_custom_api || echo "API not found"
+
+Implementation Details
+----------------------
+
+The tool extracts API specifications from three sources:
+
+1. **Source Code**: Parses KAPI specification macros using regular expressions
+2. **Vmlinux**: Reads the ``.kapi_specs`` ELF section from compiled kernels
+3. **Debugfs**: Reads from ``/sys/kernel/debug/kapi/`` filesystem interface
+
+The tool supports all KAPI specification types:
+
+- System calls (``DEFINE_KERNEL_API_SPEC``)
+- IOCTLs (``DEFINE_IOCTL_API_SPEC``)
+- Kernel functions (``KAPI_DEFINE_SPEC``)
IDE Integration
---------------
@@ -357,6 +535,11 @@ Modern IDEs can use the JSON export for:
- Context validation
- Error code documentation
+Example IDE integration::
+
+ # Generate IDE completion data
+ $ kapi --format json > .vscode/kernel-apis.json
+
Testing Framework
-----------------
@@ -367,6 +550,15 @@ The framework includes test helpers::
kapi_test_api("kmalloc", test_cases);
#endif
+The kapi tool can verify specifications against implementations::
+
+ # Run consistency tests
+ $ cd tools/kapi
+ $ ./test_consistency.sh
+
+ # Compare source vs binary specifications
+ $ ./compare_all_syscalls.sh
+
Best Practices
==============
diff --git a/tools/kapi/.gitignore b/tools/kapi/.gitignore
new file mode 100644
index 0000000000000..1390bfc12686c
--- /dev/null
+++ b/tools/kapi/.gitignore
@@ -0,0 +1,4 @@
+# Rust build artifacts
+/target/
+**/*.rs.bk
+
diff --git a/tools/kapi/Cargo.toml b/tools/kapi/Cargo.toml
new file mode 100644
index 0000000000000..4e6bcb10d132f
--- /dev/null
+++ b/tools/kapi/Cargo.toml
@@ -0,0 +1,19 @@
+[package]
+name = "kapi"
+version = "0.1.0"
+edition = "2024"
+authors = ["Sasha Levin <sashal@...nel.org>"]
+description = "Tool for extracting and displaying kernel API specifications"
+license = "GPL-2.0"
+
+[dependencies]
+goblin = "0.10"
+clap = { version = "4.4", features = ["derive"] }
+anyhow = "1.0"
+serde = { version = "1.0", features = ["derive"] }
+serde_json = "1.0"
+regex = "1.10"
+walkdir = "2.4"
+
+[dev-dependencies]
+tempfile = "3.8"
diff --git a/tools/kapi/src/extractor/debugfs.rs b/tools/kapi/src/extractor/debugfs.rs
new file mode 100644
index 0000000000000..a7e12052b96bf
--- /dev/null
+++ b/tools/kapi/src/extractor/debugfs.rs
@@ -0,0 +1,415 @@
+use anyhow::{Context, Result, bail};
+use std::fs;
+use std::io::Write;
+use std::path::PathBuf;
+use crate::formatter::OutputFormatter;
+use serde::Deserialize;
+
+use super::{ApiExtractor, ApiSpec, CapabilitySpec, display_api_spec};
+
+#[derive(Deserialize)]
+struct KernelApiJson {
+ name: String,
+ api_type: Option<String>,
+ version: Option<u32>,
+ description: Option<String>,
+ long_description: Option<String>,
+ context_flags: Option<u32>,
+ since_version: Option<String>,
+ examples: Option<String>,
+ notes: Option<String>,
+ capabilities: Option<Vec<KernelCapabilityJson>>,
+}
+
+#[derive(Deserialize)]
+struct KernelCapabilityJson {
+ capability: i32,
+ name: String,
+ action: String,
+ allows: String,
+ without_cap: String,
+ check_condition: Option<String>,
+ priority: Option<u8>,
+ alternatives: Option<Vec<i32>>,
+}
+
+/// Extractor for kernel API specifications from debugfs
+pub struct DebugfsExtractor {
+ debugfs_path: PathBuf,
+}
+
+impl DebugfsExtractor {
+ /// Create a new debugfs extractor with the specified debugfs path
+ pub fn new(debugfs_path: Option<String>) -> Result<Self> {
+ let path = match debugfs_path {
+ Some(p) => PathBuf::from(p),
+ None => PathBuf::from("/sys/kernel/debug"),
+ };
+
+ // Check if the debugfs path exists
+ if !path.exists() {
+ bail!("Debugfs path does not exist: {}", path.display());
+ }
+
+ // Check if kapi directory exists
+ let kapi_path = path.join("kapi");
+ if !kapi_path.exists() {
+ bail!("Kernel API debugfs interface not found at: {}", kapi_path.display());
+ }
+
+ Ok(Self {
+ debugfs_path: path,
+ })
+ }
+
+ /// Parse the list file to get all available API names
+ fn parse_list_file(&self) -> Result<Vec<String>> {
+ let list_path = self.debugfs_path.join("kapi/list");
+ let content = fs::read_to_string(&list_path)
+ .with_context(|| format!("Failed to read {}", list_path.display()))?;
+
+ let mut apis = Vec::new();
+ let mut in_list = false;
+
+ for line in content.lines() {
+ if line.contains("===") {
+ in_list = true;
+ continue;
+ }
+
+ if in_list && line.starts_with("Total:") {
+ break;
+ }
+
+ if in_list && !line.trim().is_empty() {
+ // Extract API name from lines like "sys_read - Read from a file descriptor"
+ if let Some(name) = line.split(" - ").next() {
+ apis.push(name.trim().to_string());
+ }
+ }
+ }
+
+ Ok(apis)
+ }
+
+ /// Try to parse JSON content, convert context flags from u32 to string representations
+ fn parse_context_flags(flags: u32) -> Vec<String> {
+ let mut result = Vec::new();
+
+ // These values should match KAPI_CTX_* flags from kernel
+ if flags & (1 << 0) != 0 { result.push("PROCESS".to_string()); }
+ if flags & (1 << 1) != 0 { result.push("SOFTIRQ".to_string()); }
+ if flags & (1 << 2) != 0 { result.push("HARDIRQ".to_string()); }
+ if flags & (1 << 3) != 0 { result.push("NMI".to_string()); }
+ if flags & (1 << 4) != 0 { result.push("ATOMIC".to_string()); }
+ if flags & (1 << 5) != 0 { result.push("SLEEPABLE".to_string()); }
+ if flags & (1 << 6) != 0 { result.push("PREEMPT_DISABLED".to_string()); }
+ if flags & (1 << 7) != 0 { result.push("IRQ_DISABLED".to_string()); }
+
+ result
+ }
+
+ /// Convert capability action from kernel representation
+ fn parse_capability_action(action: &str) -> String {
+ match action {
+ "bypass_check" => "Bypasses check".to_string(),
+ "increase_limit" => "Increases limit".to_string(),
+ "override_restriction" => "Overrides restriction".to_string(),
+ "grant_permission" => "Grants permission".to_string(),
+ "modify_behavior" => "Modifies behavior".to_string(),
+ "access_resource" => "Allows resource access".to_string(),
+ "perform_operation" => "Allows operation".to_string(),
+ _ => action.to_string(),
+ }
+ }
+
+ /// Try to parse as JSON first
+ fn try_parse_json(&self, content: &str) -> Option<ApiSpec> {
+ let json_data: KernelApiJson = serde_json::from_str(content).ok()?;
+
+ let mut spec = ApiSpec {
+ name: json_data.name,
+ api_type: json_data.api_type.unwrap_or_else(|| "unknown".to_string()),
+ description: json_data.description,
+ long_description: json_data.long_description,
+ version: json_data.version.map(|v| v.to_string()),
+ context_flags: json_data.context_flags.map_or_else(Vec::new, Self::parse_context_flags),
+ param_count: None,
+ error_count: None,
+ examples: json_data.examples,
+ notes: json_data.notes,
+ since_version: json_data.since_version,
+ subsystem: None, // Not in current JSON format
+ sysfs_path: None, // Not in current JSON format
+ permissions: None, // Not in current JSON format
+ socket_state: None,
+ protocol_behaviors: vec![],
+ addr_families: vec![],
+ buffer_spec: None,
+ async_spec: None,
+ net_data_transfer: None,
+ capabilities: vec![],
+ parameters: vec![],
+ return_spec: None,
+ errors: vec![],
+ signals: vec![],
+ signal_masks: vec![],
+ side_effects: vec![],
+ state_transitions: vec![],
+ constraints: vec![],
+ locks: vec![],
+ };
+
+ // Convert capabilities
+ if let Some(caps) = json_data.capabilities {
+ for cap in caps {
+ spec.capabilities.push(CapabilitySpec {
+ capability: cap.capability,
+ name: cap.name,
+ action: Self::parse_capability_action(&cap.action),
+ allows: cap.allows,
+ without_cap: cap.without_cap,
+ check_condition: cap.check_condition,
+ priority: cap.priority,
+ alternatives: cap.alternatives.unwrap_or_default(),
+ });
+ }
+ }
+
+ Some(spec)
+ }
+
+ /// Parse a single API specification file
+ fn parse_spec_file(&self, api_name: &str) -> Result<ApiSpec> {
+ let spec_path = self.debugfs_path.join(format!("kapi/specs/{}", api_name));
+ let content = fs::read_to_string(&spec_path)
+ .with_context(|| format!("Failed to read {}", spec_path.display()))?;
+
+ // Try JSON parsing first
+ if let Some(spec) = self.try_parse_json(&content) {
+ return Ok(spec);
+ }
+
+ // Fall back to plain text parsing
+ let mut spec = ApiSpec {
+ name: api_name.to_string(),
+ api_type: "unknown".to_string(),
+ description: None,
+ long_description: None,
+ version: None,
+ context_flags: Vec::new(),
+ param_count: None,
+ error_count: None,
+ examples: None,
+ notes: None,
+ since_version: None,
+ subsystem: None,
+ sysfs_path: None,
+ permissions: None,
+ socket_state: None,
+ protocol_behaviors: vec![],
+ addr_families: vec![],
+ buffer_spec: None,
+ async_spec: None,
+ net_data_transfer: None,
+ capabilities: vec![],
+ parameters: vec![],
+ return_spec: None,
+ errors: vec![],
+ signals: vec![],
+ signal_masks: vec![],
+ side_effects: vec![],
+ state_transitions: vec![],
+ constraints: vec![],
+ locks: vec![],
+ };
+
+ // Parse the content
+ let mut collecting_multiline = false;
+ let mut multiline_buffer = String::new();
+ let mut multiline_field = "";
+ let mut parsing_capability = false;
+ let mut current_capability: Option<CapabilitySpec> = None;
+
+ for line in content.lines() {
+ // Handle capability sections
+ if line.starts_with("Capabilities (") {
+ continue; // Skip the header
+ }
+ if line.starts_with(" ") && line.contains(" (") && line.ends_with("):") {
+ // Start of a capability entry like " CAP_IPC_LOCK (14):"
+ if let Some(cap) = current_capability.take() {
+ spec.capabilities.push(cap);
+ }
+
+ let parts: Vec<&str> = line.trim().split(" (").collect();
+ if parts.len() == 2 {
+ let cap_name = parts[0].to_string();
+ let cap_id = parts[1].trim_end_matches("):").parse().unwrap_or(0);
+ current_capability = Some(CapabilitySpec {
+ capability: cap_id,
+ name: cap_name,
+ action: String::new(),
+ allows: String::new(),
+ without_cap: String::new(),
+ check_condition: None,
+ priority: None,
+ alternatives: Vec::new(),
+ });
+ parsing_capability = true;
+ }
+ continue;
+ }
+ if parsing_capability && line.starts_with(" ") {
+ // Parse capability fields
+ if let Some(ref mut cap) = current_capability {
+ if let Some(action) = line.strip_prefix(" Action: ") {
+ cap.action = action.to_string();
+ } else if let Some(allows) = line.strip_prefix(" Allows: ") {
+ cap.allows = allows.to_string();
+ } else if let Some(without) = line.strip_prefix(" Without: ") {
+ cap.without_cap = without.to_string();
+ } else if let Some(cond) = line.strip_prefix(" Condition: ") {
+ cap.check_condition = Some(cond.to_string());
+ } else if let Some(prio) = line.strip_prefix(" Priority: ") {
+ cap.priority = prio.parse().ok();
+ } else if let Some(alts) = line.strip_prefix(" Alternatives: ") {
+ cap.alternatives = alts.split(", ")
+ .filter_map(|s| s.parse().ok())
+ .collect();
+ }
+ }
+ continue;
+ }
+ if parsing_capability && !line.starts_with(" ") {
+ // End of capabilities section
+ if let Some(cap) = current_capability.take() {
+ spec.capabilities.push(cap);
+ }
+ parsing_capability = false;
+ }
+
+ // Handle section headers
+ if line.starts_with("Parameters (") {
+ if let Some(count_str) = line.strip_prefix("Parameters (").and_then(|s| s.strip_suffix("):")) {
+ spec.param_count = count_str.parse().ok();
+ }
+ continue;
+ } else if line.starts_with("Errors (") {
+ if let Some(count_str) = line.strip_prefix("Errors (").and_then(|s| s.strip_suffix("):")) {
+ spec.error_count = count_str.parse().ok();
+ }
+ continue;
+ } else if line.starts_with("Examples:") {
+ collecting_multiline = true;
+ multiline_field = "examples";
+ multiline_buffer.clear();
+ continue;
+ } else if line.starts_with("Notes:") {
+ collecting_multiline = true;
+ multiline_field = "notes";
+ multiline_buffer.clear();
+ continue;
+ }
+
+ // Handle multiline sections
+ if collecting_multiline {
+ if line.trim().is_empty() && multiline_buffer.ends_with("\n\n") {
+ collecting_multiline = false;
+ match multiline_field {
+ "examples" => spec.examples = Some(multiline_buffer.trim().to_string()),
+ "notes" => spec.notes = Some(multiline_buffer.trim().to_string()),
+ _ => {}
+ }
+ multiline_buffer.clear();
+ } else {
+ if !multiline_buffer.is_empty() {
+ multiline_buffer.push('\n');
+ }
+ multiline_buffer.push_str(line);
+ }
+ continue;
+ }
+
+ // Parse regular fields
+ if let Some(desc) = line.strip_prefix("Description: ") {
+ spec.description = Some(desc.to_string());
+ } else if let Some(long_desc) = line.strip_prefix("Long description: ") {
+ spec.long_description = Some(long_desc.to_string());
+ } else if let Some(version) = line.strip_prefix("Version: ") {
+ spec.version = Some(version.to_string());
+ } else if let Some(since) = line.strip_prefix("Since: ") {
+ spec.since_version = Some(since.to_string());
+ } else if let Some(flags) = line.strip_prefix("Context flags: ") {
+ spec.context_flags = flags.split_whitespace()
+ .map(str::to_string)
+ .collect();
+ } else if let Some(subsys) = line.strip_prefix("Subsystem: ") {
+ spec.subsystem = Some(subsys.to_string());
+ } else if let Some(path) = line.strip_prefix("Sysfs Path: ") {
+ spec.sysfs_path = Some(path.to_string());
+ } else if let Some(perms) = line.strip_prefix("Permissions: ") {
+ spec.permissions = Some(perms.to_string());
+ }
+ }
+
+ // Handle any remaining capability
+ if let Some(cap) = current_capability.take() {
+ spec.capabilities.push(cap);
+ }
+
+ // Determine API type based on name
+ if api_name.starts_with("sys_") {
+ spec.api_type = "syscall".to_string();
+ } else if api_name.contains("_ioctl") || api_name.starts_with("ioctl_") {
+ spec.api_type = "ioctl".to_string();
+ } else if api_name.contains("sysfs") || api_name.ends_with("_show") || api_name.ends_with("_store") {
+ spec.api_type = "sysfs".to_string();
+ } else {
+ spec.api_type = "function".to_string();
+ }
+
+ Ok(spec)
+ }
+}
+
+impl ApiExtractor for DebugfsExtractor {
+ fn extract_all(&self) -> Result<Vec<ApiSpec>> {
+ let api_names = self.parse_list_file()?;
+ let mut specs = Vec::new();
+
+ for name in api_names {
+ match self.parse_spec_file(&name) {
+ Ok(spec) => specs.push(spec),
+ Err(_e) => {}, // Silently skip files that fail to parse
+ }
+ }
+
+ Ok(specs)
+ }
+
+ fn extract_by_name(&self, name: &str) -> Result<Option<ApiSpec>> {
+ let api_names = self.parse_list_file()?;
+
+ if api_names.contains(&name.to_string()) {
+ Ok(Some(self.parse_spec_file(name)?))
+ } else {
+ Ok(None)
+ }
+ }
+
+ fn display_api_details(
+ &self,
+ api_name: &str,
+ formatter: &mut dyn OutputFormatter,
+ writer: &mut dyn Write,
+ ) -> Result<()> {
+ if let Some(spec) = self.extract_by_name(api_name)? {
+ display_api_spec(&spec, formatter, writer)?;
+ } else {
+ writeln!(writer, "API '{api_name}' not found in debugfs")?;
+ }
+
+ Ok(())
+ }
+}
\ No newline at end of file
diff --git a/tools/kapi/src/extractor/mod.rs b/tools/kapi/src/extractor/mod.rs
new file mode 100644
index 0000000000000..644eb7cf64fd9
--- /dev/null
+++ b/tools/kapi/src/extractor/mod.rs
@@ -0,0 +1,411 @@
+use anyhow::Result;
+use std::io::Write;
+use std::convert::TryInto;
+use crate::formatter::OutputFormatter;
+
+pub mod vmlinux;
+pub mod source_parser;
+pub mod debugfs;
+
+pub use vmlinux::VmlinuxExtractor;
+pub use source_parser::SourceExtractor;
+pub use debugfs::DebugfsExtractor;
+
+/// Socket state specification
+#[derive(Debug, Clone, serde::Serialize)]
+pub struct SocketStateSpec {
+ pub required_states: Vec<String>,
+ pub forbidden_states: Vec<String>,
+ pub resulting_state: Option<String>,
+ pub condition: Option<String>,
+ pub applicable_protocols: Option<String>,
+}
+
+/// Protocol behavior specification
+#[derive(Debug, Clone, serde::Serialize)]
+pub struct ProtocolBehaviorSpec {
+ pub applicable_protocols: String,
+ pub behavior: String,
+ pub protocol_flags: Option<String>,
+ pub flag_description: Option<String>,
+}
+
+/// Address family specification
+#[derive(Debug, Clone, serde::Serialize)]
+pub struct AddrFamilySpec {
+ pub family: i32,
+ pub family_name: String,
+ pub addr_struct_size: usize,
+ pub min_addr_len: usize,
+ pub max_addr_len: usize,
+ pub addr_format: Option<String>,
+ pub supports_wildcard: bool,
+ pub supports_multicast: bool,
+ pub supports_broadcast: bool,
+ pub special_addresses: Option<String>,
+ pub port_range_min: u32,
+ pub port_range_max: u32,
+}
+
+/// Buffer specification
+#[derive(Debug, Clone, serde::Serialize)]
+pub struct BufferSpec {
+ pub buffer_behaviors: Option<String>,
+ pub min_buffer_size: Option<usize>,
+ pub max_buffer_size: Option<usize>,
+ pub optimal_buffer_size: Option<usize>,
+}
+
+/// Async specification
+#[derive(Debug, Clone, serde::Serialize)]
+pub struct AsyncSpec {
+ pub supported_modes: Option<String>,
+ pub nonblock_errno: Option<i32>,
+}
+
+/// Capability specification
+#[derive(Debug, Clone, serde::Serialize)]
+pub struct CapabilitySpec {
+ pub capability: i32,
+ pub name: String,
+ pub action: String,
+ pub allows: String,
+ pub without_cap: String,
+ pub check_condition: Option<String>,
+ pub priority: Option<u8>,
+ pub alternatives: Vec<i32>,
+}
+
+/// Parameter specification
+#[derive(Debug, Clone, serde::Serialize)]
+pub struct ParamSpec {
+ pub index: u32,
+ pub name: String,
+ pub type_name: String,
+ pub description: String,
+ pub flags: u32,
+ pub param_type: u32,
+ pub constraint_type: u32,
+ pub constraint: Option<String>,
+ pub min_value: Option<i64>,
+ pub max_value: Option<i64>,
+ pub valid_mask: Option<u64>,
+ pub enum_values: Vec<String>,
+ pub size: Option<u32>,
+ pub alignment: Option<u32>,
+}
+
+/// Return value specification
+#[derive(Debug, Clone, serde::Serialize)]
+pub struct ReturnSpec {
+ pub type_name: String,
+ pub description: String,
+ pub return_type: u32,
+ pub check_type: u32,
+ pub success_value: Option<i64>,
+ pub success_min: Option<i64>,
+ pub success_max: Option<i64>,
+ pub error_values: Vec<i32>,
+}
+
+/// Error specification
+#[derive(Debug, Clone, serde::Serialize)]
+pub struct ErrorSpec {
+ pub error_code: i32,
+ pub name: String,
+ pub condition: String,
+ pub description: String,
+}
+
+/// Signal specification
+#[derive(Debug, Clone, serde::Serialize)]
+pub struct SignalSpec {
+ pub signal_num: i32,
+ pub signal_name: String,
+ pub direction: u32,
+ pub action: u32,
+ pub target: Option<String>,
+ pub condition: Option<String>,
+ pub description: Option<String>,
+ pub timing: u32,
+ pub priority: u32,
+ pub restartable: bool,
+ pub interruptible: bool,
+ pub queue: Option<String>,
+ pub sa_flags: u32,
+ pub sa_flags_required: u32,
+ pub sa_flags_forbidden: u32,
+ pub state_required: u32,
+ pub state_forbidden: u32,
+ pub error_on_signal: Option<i32>,
+}
+
+/// Signal mask specification
+#[derive(Debug, Clone, serde::Serialize)]
+pub struct SignalMaskSpec {
+ pub name: String,
+ pub description: String,
+}
+
+/// Side effect specification
+#[derive(Debug, Clone, serde::Serialize)]
+pub struct SideEffectSpec {
+ pub effect_type: u32,
+ pub target: String,
+ pub condition: Option<String>,
+ pub description: String,
+ pub reversible: bool,
+}
+
+/// State transition specification
+#[derive(Debug, Clone, serde::Serialize)]
+pub struct StateTransitionSpec {
+ pub object: String,
+ pub from_state: String,
+ pub to_state: String,
+ pub condition: Option<String>,
+ pub description: String,
+}
+
+/// Constraint specification
+#[derive(Debug, Clone, serde::Serialize)]
+pub struct ConstraintSpec {
+ pub name: String,
+ pub description: String,
+ pub expression: Option<String>,
+}
+
+/// Lock specification
+#[derive(Debug, Clone, serde::Serialize)]
+pub struct LockSpec {
+ pub lock_name: String,
+ pub lock_type: u32,
+ pub acquired: bool,
+ pub released: bool,
+ pub held_on_entry: bool,
+ pub held_on_exit: bool,
+ pub description: String,
+}
+
+/// Common API specification information that all extractors should provide
+#[derive(Debug, Clone)]
+pub struct ApiSpec {
+ pub name: String,
+ pub api_type: String,
+ pub description: Option<String>,
+ pub long_description: Option<String>,
+ pub version: Option<String>,
+ pub context_flags: Vec<String>,
+ pub param_count: Option<u32>,
+ pub error_count: Option<u32>,
+ pub examples: Option<String>,
+ pub notes: Option<String>,
+ pub since_version: Option<String>,
+ // Sysfs-specific fields
+ pub subsystem: Option<String>,
+ pub sysfs_path: Option<String>,
+ pub permissions: Option<String>,
+ // Networking-specific fields
+ pub socket_state: Option<SocketStateSpec>,
+ pub protocol_behaviors: Vec<ProtocolBehaviorSpec>,
+ pub addr_families: Vec<AddrFamilySpec>,
+ pub buffer_spec: Option<BufferSpec>,
+ pub async_spec: Option<AsyncSpec>,
+ pub net_data_transfer: Option<String>,
+ pub capabilities: Vec<CapabilitySpec>,
+ pub parameters: Vec<ParamSpec>,
+ pub return_spec: Option<ReturnSpec>,
+ pub errors: Vec<ErrorSpec>,
+ pub signals: Vec<SignalSpec>,
+ pub signal_masks: Vec<SignalMaskSpec>,
+ pub side_effects: Vec<SideEffectSpec>,
+ pub state_transitions: Vec<StateTransitionSpec>,
+ pub constraints: Vec<ConstraintSpec>,
+ pub locks: Vec<LockSpec>,
+}
+
+/// Trait for extracting API specifications from different sources
+pub trait ApiExtractor {
+ /// Extract all API specifications from the source
+ fn extract_all(&self) -> Result<Vec<ApiSpec>>;
+
+ /// Extract a specific API specification by name
+ fn extract_by_name(&self, name: &str) -> Result<Option<ApiSpec>>;
+
+ /// Display detailed information about a specific API
+ fn display_api_details(
+ &self,
+ api_name: &str,
+ formatter: &mut dyn OutputFormatter,
+ writer: &mut dyn Write,
+ ) -> Result<()>;
+}
+
+/// Helper function to display an ApiSpec using a formatter
+pub fn display_api_spec(
+ spec: &ApiSpec,
+ formatter: &mut dyn OutputFormatter,
+ writer: &mut dyn Write,
+) -> Result<()> {
+ formatter.begin_api_details(writer, &spec.name)?;
+
+ if let Some(desc) = &spec.description {
+ formatter.description(writer, desc)?;
+ }
+
+ if let Some(long_desc) = &spec.long_description {
+ formatter.long_description(writer, long_desc)?;
+ }
+
+ if let Some(version) = &spec.since_version {
+ formatter.since_version(writer, version)?;
+ }
+
+ if !spec.context_flags.is_empty() {
+ formatter.begin_context_flags(writer)?;
+ for flag in &spec.context_flags {
+ formatter.context_flag(writer, flag)?;
+ }
+ formatter.end_context_flags(writer)?;
+ }
+
+ if !spec.parameters.is_empty() {
+ formatter.begin_parameters(writer, spec.parameters.len().try_into().unwrap_or(u32::MAX))?;
+ for param in &spec.parameters {
+ formatter.parameter(writer, param)?;
+ }
+ formatter.end_parameters(writer)?;
+ }
+
+ if let Some(ret) = &spec.return_spec {
+ formatter.return_spec(writer, ret)?;
+ }
+
+ if !spec.errors.is_empty() {
+ formatter.begin_errors(writer, spec.errors.len().try_into().unwrap_or(u32::MAX))?;
+ for error in &spec.errors {
+ formatter.error(writer, error)?;
+ }
+ formatter.end_errors(writer)?;
+ }
+
+ if let Some(notes) = &spec.notes {
+ formatter.notes(writer, notes)?;
+ }
+
+ if let Some(examples) = &spec.examples {
+ formatter.examples(writer, examples)?;
+ }
+
+ // Display sysfs-specific fields
+ if spec.api_type == "sysfs" {
+ if let Some(subsystem) = &spec.subsystem {
+ formatter.sysfs_subsystem(writer, subsystem)?;
+ }
+ if let Some(path) = &spec.sysfs_path {
+ formatter.sysfs_path(writer, path)?;
+ }
+ if let Some(perms) = &spec.permissions {
+ formatter.sysfs_permissions(writer, perms)?;
+ }
+ }
+
+ // Display networking-specific fields
+ if let Some(socket_state) = &spec.socket_state {
+ formatter.socket_state(writer, socket_state)?;
+ }
+
+ if !spec.protocol_behaviors.is_empty() {
+ formatter.begin_protocol_behaviors(writer)?;
+ for behavior in &spec.protocol_behaviors {
+ formatter.protocol_behavior(writer, behavior)?;
+ }
+ formatter.end_protocol_behaviors(writer)?;
+ }
+
+ if !spec.addr_families.is_empty() {
+ formatter.begin_addr_families(writer)?;
+ for family in &spec.addr_families {
+ formatter.addr_family(writer, family)?;
+ }
+ formatter.end_addr_families(writer)?;
+ }
+
+ if let Some(buffer_spec) = &spec.buffer_spec {
+ formatter.buffer_spec(writer, buffer_spec)?;
+ }
+
+ if let Some(async_spec) = &spec.async_spec {
+ formatter.async_spec(writer, async_spec)?;
+ }
+
+ if let Some(net_data_transfer) = &spec.net_data_transfer {
+ formatter.net_data_transfer(writer, net_data_transfer)?;
+ }
+
+ if !spec.capabilities.is_empty() {
+ formatter.begin_capabilities(writer)?;
+ for cap in &spec.capabilities {
+ formatter.capability(writer, cap)?;
+ }
+ formatter.end_capabilities(writer)?;
+ }
+
+ // Display signals
+ if !spec.signals.is_empty() {
+ formatter.begin_signals(writer, spec.signals.len().try_into().unwrap_or(u32::MAX))?;
+ for signal in &spec.signals {
+ formatter.signal(writer, signal)?;
+ }
+ formatter.end_signals(writer)?;
+ }
+
+ // Display signal masks
+ if !spec.signal_masks.is_empty() {
+ formatter.begin_signal_masks(writer, spec.signal_masks.len().try_into().unwrap_or(u32::MAX))?;
+ for mask in &spec.signal_masks {
+ formatter.signal_mask(writer, mask)?;
+ }
+ formatter.end_signal_masks(writer)?;
+ }
+
+ // Display side effects
+ if !spec.side_effects.is_empty() {
+ formatter.begin_side_effects(writer, spec.side_effects.len().try_into().unwrap_or(u32::MAX))?;
+ for effect in &spec.side_effects {
+ formatter.side_effect(writer, effect)?;
+ }
+ formatter.end_side_effects(writer)?;
+ }
+
+ // Display state transitions
+ if !spec.state_transitions.is_empty() {
+ formatter.begin_state_transitions(writer, spec.state_transitions.len().try_into().unwrap_or(u32::MAX))?;
+ for trans in &spec.state_transitions {
+ formatter.state_transition(writer, trans)?;
+ }
+ formatter.end_state_transitions(writer)?;
+ }
+
+ // Display constraints
+ if !spec.constraints.is_empty() {
+ formatter.begin_constraints(writer, spec.constraints.len().try_into().unwrap_or(u32::MAX))?;
+ for constraint in &spec.constraints {
+ formatter.constraint(writer, constraint)?;
+ }
+ formatter.end_constraints(writer)?;
+ }
+
+ // Display locks
+ if !spec.locks.is_empty() {
+ formatter.begin_locks(writer, spec.locks.len().try_into().unwrap_or(u32::MAX))?;
+ for lock in &spec.locks {
+ formatter.lock(writer, lock)?;
+ }
+ formatter.end_locks(writer)?;
+ }
+
+ formatter.end_api_details(writer)?;
+
+ Ok(())
+}
\ No newline at end of file
diff --git a/tools/kapi/src/extractor/source_parser.rs b/tools/kapi/src/extractor/source_parser.rs
new file mode 100644
index 0000000000000..bec036a56e40f
--- /dev/null
+++ b/tools/kapi/src/extractor/source_parser.rs
@@ -0,0 +1,1625 @@
+use anyhow::{Context, Result};
+use regex::Regex;
+use std::fs;
+use std::path::Path;
+use std::collections::HashMap;
+use walkdir::WalkDir;
+use std::io::Write;
+use crate::formatter::OutputFormatter;
+use super::{ApiExtractor, ApiSpec, CapabilitySpec, display_api_spec,
+ SocketStateSpec, ProtocolBehaviorSpec, AddrFamilySpec, BufferSpec, AsyncSpec,
+ StateTransitionSpec, SideEffectSpec, ParamSpec, ReturnSpec, ErrorSpec, LockSpec, ConstraintSpec};
+
+#[derive(Debug, Clone)]
+pub struct SourceApiSpec {
+ pub name: String,
+ pub api_type: ApiType,
+ pub parsed_fields: HashMap<String, String>,
+}
+
+#[derive(Debug, Clone, PartialEq)]
+pub enum ApiType {
+ Syscall,
+ Ioctl,
+ Function,
+ Sysfs,
+ Unknown,
+}
+
+impl ApiType {
+ fn from_name(name: &str) -> Self {
+ if name.starts_with("sys_") {
+ ApiType::Syscall
+ } else if name.contains("ioctl") || name.contains("IOCTL") {
+ ApiType::Ioctl
+ } else if name.starts_with("do_") || name.starts_with("__") {
+ ApiType::Function
+ } else {
+ ApiType::Unknown
+ }
+ }
+}
+
+pub struct SourceParser {
+ // Regex patterns for matching KAPI specifications
+ spec_start_pattern: Regex,
+ spec_end_pattern: Regex,
+ ioctl_spec_pattern: Regex,
+ sysfs_spec_pattern: Regex,
+ // Networking-specific patterns
+ socket_state_req_pattern: Regex,
+ socket_state_result_pattern: Regex,
+ socket_state_cond_pattern: Regex,
+ socket_state_protos_pattern: Regex,
+ protocol_behavior_pattern: Regex,
+ protocol_flags_pattern: Regex,
+ addr_family_pattern: Regex,
+ addr_format_pattern: Regex,
+ addr_features_pattern: Regex,
+ addr_special_pattern: Regex,
+ addr_ports_pattern: Regex,
+ buffer_spec_pattern: Regex,
+ async_spec_pattern: Regex,
+ net_data_transfer_pattern: Regex,
+}
+
+impl SourceParser {
+ pub fn new() -> Result<Self> {
+ Ok(SourceParser {
+ // Match DEFINE_KERNEL_API_SPEC(function_name)
+ spec_start_pattern: Regex::new(r"DEFINE_KERNEL_API_SPEC\s*\(\s*([a-zA-Z_][a-zA-Z0-9_]*)\s*\)")?,
+ // Match KAPI_END_SPEC
+ spec_end_pattern: Regex::new(r"KAPI_END_SPEC")?,
+ // Match IOCTL specifications
+ ioctl_spec_pattern: Regex::new(r#"DEFINE_IOCTL_API_SPEC\s*\(\s*([a-zA-Z_][a-zA-Z0-9_]*)\s*,\s*([^,]+)\s*,\s*"([^"]+)"\s*\)"#)?,
+ // Match SYSFS specifications
+ sysfs_spec_pattern: Regex::new(r"DEFINE_SYSFS_API_SPEC\s*\(\s*([a-zA-Z_][a-zA-Z0-9_]*)\s*\)")?,
+ // Networking-specific patterns
+ socket_state_req_pattern: Regex::new(r"KAPI_SOCKET_STATE_REQ\s*\(\s*([^)]+)\s*\)")?,
+ socket_state_result_pattern: Regex::new(r"KAPI_SOCKET_STATE_RESULT\s*\(\s*([^)]+)\s*\)")?,
+ socket_state_cond_pattern: Regex::new(r#"KAPI_SOCKET_STATE_COND\s*\(\s*"([^"]*)"\s*\)"#)?,
+ socket_state_protos_pattern: Regex::new(r"KAPI_SOCKET_STATE_PROTOS\s*\(\s*([^)]+)\s*\)")?,
+ protocol_behavior_pattern: Regex::new(r#"KAPI_PROTOCOL_BEHAVIOR\s*\(\s*(\d+)\s*,\s*([^,]+)\s*,\s*"([^"]*(?:\s*"[^"]*)*?)"\s*\)"#)?,
+ protocol_flags_pattern: Regex::new(r#"KAPI_PROTOCOL_FLAGS\s*\(\s*(\d+)\s*,\s*"([^"]*)"\s*\)"#)?,
+ addr_family_pattern: Regex::new(r#"KAPI_ADDR_FAMILY\s*\(\s*(\d+)\s*,\s*([^,]+)\s*,\s*"([^"]+)"\s*,\s*([^,]+)\s*,\s*(\d+)\s*,\s*(\d+)\s*\)"#)?,
+ addr_format_pattern: Regex::new(r#"KAPI_ADDR_FORMAT\s*\(\s*"([^"]*)"\s*\)"#)?,
+ addr_features_pattern: Regex::new(r"KAPI_ADDR_FEATURES\s*\(\s*(true|false)\s*,\s*(true|false)\s*,\s*(true|false)\s*\)")?,
+ addr_special_pattern: Regex::new(r#"KAPI_ADDR_SPECIAL\s*\(\s*"([^"]*(?:\s*"[^"]*)*?)"\s*\)"#)?,
+ addr_ports_pattern: Regex::new(r"KAPI_ADDR_PORTS\s*\(\s*(\d+)\s*,\s*(\d+)\s*\)")?,
+ buffer_spec_pattern: Regex::new(r"KAPI_BUFFER_SPEC\s*\(\s*(\d+)\s*\)")?,
+ async_spec_pattern: Regex::new(r"KAPI_ASYNC_SPEC\s*\(\s*([^,]+)\s*,\s*(\d+)\s*\)")?,
+ net_data_transfer_pattern: Regex::new(r#"KAPI_NET_DATA_TRANSFER\s*\(\s*"([^"]*)"\s*\)"#)?,
+ })
+ }
+
+ /// Parse a single source file for KAPI specifications
+ pub fn parse_file(&self, path: &Path) -> Result<Vec<SourceApiSpec>> {
+ let content = fs::read_to_string(path)
+ .with_context(|| format!("Failed to read file: {}", path.display()))?;
+
+ self.parse_content(&content, path)
+ }
+
+ /// Parse file content for KAPI specifications
+ pub fn parse_content(&self, content: &str, _file_path: &Path) -> Result<Vec<SourceApiSpec>> {
+ let mut specs = Vec::new();
+ let lines: Vec<&str> = content.lines().collect();
+
+ // First, look for standard KAPI specs
+ for (i, line) in lines.iter().enumerate() {
+ if let Some(captures) = self.spec_start_pattern.captures(line) {
+ let api_name = captures.get(1).unwrap().as_str().to_string();
+
+ // Find the end of this specification
+ if let Some(spec_content) = self.extract_spec_block(&lines, i) {
+ let mut spec = SourceApiSpec {
+ name: api_name.clone(),
+ api_type: ApiType::from_name(&api_name),
+ parsed_fields: HashMap::new(),
+ };
+
+ // Parse the fields
+ self.parse_spec_fields(&spec_content, &mut spec.parsed_fields)?;
+
+ specs.push(spec);
+ }
+ }
+
+ // Also look for IOCTL specs
+ if let Some(captures) = self.ioctl_spec_pattern.captures(line) {
+ let spec_name = captures.get(1).unwrap().as_str().to_string();
+ let cmd = captures.get(2).unwrap().as_str().to_string();
+ let cmd_name = captures.get(3).unwrap().as_str().to_string();
+
+ // Find the end of this IOCTL specification
+ if let Some(spec_content) = self.extract_ioctl_spec_block(&lines, i) {
+ let mut spec = SourceApiSpec {
+ name: spec_name,
+ api_type: ApiType::Ioctl,
+ parsed_fields: HashMap::new(),
+ };
+
+ // Add IOCTL-specific fields
+ spec.parsed_fields.insert("cmd".to_string(), cmd);
+ spec.parsed_fields.insert("cmd_name".to_string(), cmd_name);
+
+ // Parse other fields
+ self.parse_spec_fields(&spec_content, &mut spec.parsed_fields)?;
+
+ specs.push(spec);
+ }
+ }
+
+ // Also look for SYSFS specs
+ if let Some(captures) = self.sysfs_spec_pattern.captures(line) {
+ let attr_name = captures.get(1).unwrap().as_str().to_string();
+
+ // Find the end of this specification
+ if let Some(spec_content) = self.extract_spec_block(&lines, i) {
+ let mut spec = SourceApiSpec {
+ name: attr_name,
+ api_type: ApiType::Sysfs,
+ parsed_fields: HashMap::new(),
+ };
+
+ // Parse the fields
+ self.parse_spec_fields(&spec_content, &mut spec.parsed_fields)?;
+
+ specs.push(spec);
+ }
+ }
+ }
+
+ Ok(specs)
+ }
+
+ /// Extract a complete KAPI specification block from the source
+ fn extract_spec_block(&self, lines: &[&str], start_idx: usize) -> Option<String> {
+ let mut spec_lines = Vec::new();
+
+ for (_i, line) in lines.iter().enumerate().skip(start_idx) {
+ spec_lines.push((*line).to_string());
+
+ // Check for end of spec
+ if self.spec_end_pattern.is_match(line) {
+ return Some(spec_lines.join("\n"));
+ }
+ }
+
+ None
+ }
+
+ /// Extract a complete IOCTL specification block
+ fn extract_ioctl_spec_block(&self, lines: &[&str], start_idx: usize) -> Option<String> {
+ let mut spec_lines = Vec::new();
+ let mut brace_count = 0;
+
+ for (i, line) in lines.iter().enumerate().skip(start_idx) {
+ spec_lines.push((*line).to_string());
+
+ // Count braces
+ for ch in line.chars() {
+ match ch {
+ '{' => brace_count += 1,
+ '}' => brace_count -= 1,
+ _ => {}
+ }
+ }
+
+ // Check for end patterns
+ if line.contains("KAPI_END_IOCTL_SPEC") || line.contains("KAPI_IOCTL_END_SPEC") {
+ return Some(spec_lines.join("\n"));
+ }
+
+ // Alternative end: closing brace with semicolon at top level
+ if brace_count == 0 && line.contains("};") && i > start_idx {
+ return Some(spec_lines.join("\n"));
+ }
+ }
+
+ None
+ }
+
+ /// Parse individual KAPI fields from the specification
+ fn parse_spec_fields(&self, content: &str, fields: &mut HashMap<String, String>) -> Result<()> {
+ // Parse KAPI_DESCRIPTION
+ if let Some(captures) = Regex::new(r#"KAPI_DESCRIPTION\s*\(\s*"([^"]*)"\s*\)"#)?.captures(content) {
+ fields.insert("description".to_string(), captures.get(1).unwrap().as_str().to_string());
+ }
+
+ // Parse KAPI_LONG_DESC (handle multi-line)
+ if let Some(captures) = Regex::new(r#"KAPI_LONG_DESC\s*\(\s*"([^"]*(?:\s*"[^"]*)*?)"\s*\)"#)?.captures(content) {
+ let long_desc = captures.get(1).unwrap().as_str()
+ .replace("\"\n\t\t \"", " ")
+ .replace("\"\n\t\t \"", " ")
+ .replace("\"\n\t\t \"", " ")
+ .replace("\"\n\t\t \"", " ")
+ .replace("\"\n\t\t \"", " ")
+ .replace("\"\n\t\t\"", " ");
+ fields.insert("long_description".to_string(), long_desc);
+ }
+
+ // Parse KAPI_CONTEXT
+ if let Some(captures) = Regex::new(r"KAPI_CONTEXT\s*\(([^)]+)\)")?.captures(content) {
+ fields.insert("context".to_string(), captures.get(1).unwrap().as_str().to_string());
+ }
+
+ // Parse KAPI_NOTES (handle multi-line)
+ if let Some(captures) = Regex::new(r#"KAPI_NOTES\s*\(\s*"([^"]*(?:\s*"[^"]*)*?)"\s*\)"#)?.captures(content) {
+ let notes = captures.get(1).unwrap().as_str()
+ .replace("\"\n\t\t \"", "\n")
+ .replace("\"\n\t\t \"", "\n")
+ .replace("\"\n\t\t \"", "\n")
+ .replace("\"\n\t\t\"", "\n")
+ .replace("\\n", "\n")
+ .replace("\\\"", "\"")
+ .trim()
+ .to_string();
+ fields.insert("notes".to_string(), notes);
+ }
+
+ // Parse KAPI_EXAMPLES (handle multi-line)
+ if let Some(captures) = Regex::new(r#"KAPI_EXAMPLES\s*\(\s*"([^"]*(?:\s*"[^"]*)*?)"\s*\)"#)?.captures(content) {
+ let examples = captures.get(1).unwrap().as_str()
+ .replace("\"\n\t\t \"", "")
+ .replace("\"\n\t\t \"", "")
+ .replace("\"\n\t\t \"", "")
+ .replace("\"\n\t\t \"", "")
+ .replace("\"\n\t\t \"", "")
+ .replace("\"\n\t\t \"", "")
+ .replace("\"\n\t\t\"", "")
+ .replace("\\n\\n", "\n\n")
+ .replace("\\n", "\n")
+ .replace("\\\"", "\"")
+ .replace("\\\\", "\\")
+ .trim()
+ .to_string();
+ fields.insert("examples".to_string(), examples);
+ }
+
+ // Parse KAPI_SINCE_VERSION
+ if let Some(captures) = Regex::new(r#"KAPI_SINCE_VERSION\s*\(\s*"([^"]*)"\s*\)"#)?.captures(content) {
+ fields.insert("since_version".to_string(), captures.get(1).unwrap().as_str().to_string());
+ }
+
+ // Parse parameter count
+ let param_regex = Regex::new(r"KAPI_PARAM\s*\(\s*(\d+)\s*,")?;
+ let mut max_param_idx = 0;
+ for captures in param_regex.captures_iter(content) {
+ if let Ok(idx) = captures.get(1).unwrap().as_str().parse::<usize>() {
+ max_param_idx = max_param_idx.max(idx + 1);
+ }
+ }
+ if max_param_idx > 0 {
+ fields.insert("param_count".to_string(), max_param_idx.to_string());
+ }
+
+ // Parse error count
+ let error_regex = Regex::new(r"KAPI_ERROR\s*\(\s*(\d+)\s*,")?;
+ let mut max_error_idx = 0;
+ for captures in error_regex.captures_iter(content) {
+ if let Ok(idx) = captures.get(1).unwrap().as_str().parse::<usize>() {
+ max_error_idx = max_error_idx.max(idx + 1);
+ }
+ }
+ if max_error_idx > 0 {
+ fields.insert("error_count".to_string(), max_error_idx.to_string());
+ }
+
+ // Parse other counts
+ if content.contains(".error_count =") {
+ if let Some(captures) = Regex::new(r"\.error_count\s*=\s*(\d+)")?.captures(content) {
+ fields.insert("error_count".to_string(), captures.get(1).unwrap().as_str().to_string());
+ }
+ }
+
+ // Parse capability count
+ if let Some(captures) = Regex::new(r"KAPI_CAPABILITY_COUNT\s*\(\s*(\d+)\s*\)")?.captures(content) {
+ fields.insert("capability_count".to_string(), captures.get(1).unwrap().as_str().to_string());
+ }
+
+ // Also check for .capability_count = N
+ if content.contains(".capability_count =") {
+ if let Some(captures) = Regex::new(r"\.capability_count\s*=\s*(\d+)")?.captures(content) {
+ fields.insert("capability_count".to_string(), captures.get(1).unwrap().as_str().to_string());
+ }
+ }
+
+ // Parse capabilities
+ let cap_regex = Regex::new(r#"KAPI_CAPABILITY\s*\(\s*(\d+)\s*,\s*(\d+)\s*,\s*"([^"]+)"\s*,\s*([A-Z_]+)\s*\)"#)?;
+ let mut capabilities = Vec::new();
+ for captures in cap_regex.captures_iter(content) {
+ let idx = captures.get(1).unwrap().as_str().parse::<usize>().unwrap_or(0);
+ let cap_id = captures.get(2).unwrap().as_str();
+ let cap_name = captures.get(3).unwrap().as_str();
+ let cap_action = captures.get(4).unwrap().as_str();
+
+ // Store capability info - we'll parse the details separately
+ let cap_key = format!("capability_{}", idx);
+ fields.insert(format!("{}_id", cap_key), cap_id.to_string());
+ fields.insert(format!("{}_name", cap_key), cap_name.to_string());
+ fields.insert(format!("{}_action", cap_key), cap_action.to_string());
+ capabilities.push(idx);
+ }
+
+ // Pre-compile capability regex patterns
+ let cap_allows_pattern = Regex::new(r#"KAPI_CAP_ALLOWS\s*\(\s*"([^"]*)"\s*\)"#)?;
+ let cap_without_pattern = Regex::new(r#"KAPI_CAP_WITHOUT\s*\(\s*"([^"]*)"\s*\)"#)?;
+ let cap_condition_pattern = Regex::new(r#"KAPI_CAP_CONDITION\s*\(\s*"([^"]*)"\s*\)"#)?;
+ let cap_priority_pattern = Regex::new(r"KAPI_CAP_PRIORITY\s*\(\s*(\d+)\s*\)")?;
+
+ // Parse capability details for each found capability
+ for idx in capabilities {
+ let cap_key = format!("capability_{}", idx);
+
+ // Find the capability block and parse its fields
+ if let Some(cap_start) = content.find(&format!("KAPI_CAPABILITY({},", idx)) {
+ if let Some(cap_end) = content[cap_start..].find("KAPI_CAPABILITY_END") {
+ let cap_content = &content[cap_start..cap_start + cap_end];
+
+ // Parse KAPI_CAP_ALLOWS
+ if let Some(captures) = cap_allows_pattern.captures(cap_content) {
+ fields.insert(format!("{}_allows", cap_key), captures.get(1).unwrap().as_str().to_string());
+ }
+
+ // Parse KAPI_CAP_WITHOUT
+ if let Some(captures) = cap_without_pattern.captures(cap_content) {
+ fields.insert(format!("{}_without", cap_key), captures.get(1).unwrap().as_str().to_string());
+ }
+
+ // Parse KAPI_CAP_CONDITION
+ if let Some(captures) = cap_condition_pattern.captures(cap_content) {
+ fields.insert(format!("{}_condition", cap_key), captures.get(1).unwrap().as_str().to_string());
+ }
+
+ // Parse KAPI_CAP_PRIORITY
+ if let Some(captures) = cap_priority_pattern.captures(cap_content) {
+ fields.insert(format!("{}_priority", cap_key), captures.get(1).unwrap().as_str().to_string());
+ }
+ }
+ }
+ }
+
+ if content.contains(".param_count =") {
+ if let Some(captures) = Regex::new(r"\.param_count\s*=\s*(\d+)")?.captures(content) {
+ fields.insert("param_count".to_string(), captures.get(1).unwrap().as_str().to_string());
+ }
+ }
+
+ // Parse .since_version
+ if let Some(captures) = Regex::new(r#"\.since_version\s*=\s*"([^"]*)""#)?.captures(content) {
+ fields.insert("since_version".to_string(), captures.get(1).unwrap().as_str().to_string());
+ }
+
+ // Parse .notes (handle multi-line)
+ if let Some(captures) = Regex::new(r#"\.notes\s*=\s*"([^"]*(?:\s*"[^"]*)*?)""#)?.captures(content) {
+ let notes = captures.get(1).unwrap().as_str()
+ .replace("\"\n\t\t \"", " ")
+ .replace("\"\n\t\t\"", " ")
+ .replace("\"\n\t \"", " ") // Handle single tab + space
+ .trim()
+ .to_string();
+ fields.insert("notes".to_string(), notes);
+ }
+
+ // Parse .examples (handle multi-line)
+ if let Some(captures) = Regex::new(r#"\.examples\s*=\s*"([^"]*(?:\s*"[^"]*)*?)""#)?.captures(content) {
+ let examples = captures.get(1).unwrap().as_str()
+ .replace("\\n\"\n\t\t \"", "\n")
+ .replace("\\n", "\n");
+ fields.insert("examples".to_string(), examples);
+ }
+
+ // Parse sysfs-specific fields
+ // Parse KAPI_SUBSYSTEM
+ if let Some(captures) = Regex::new(r#"KAPI_SUBSYSTEM\s*\(\s*"([^"]*)"\s*\)"#)?.captures(content) {
+ fields.insert("subsystem".to_string(), captures.get(1).unwrap().as_str().to_string());
+ }
+
+ // Parse .subsystem =
+ if let Some(captures) = Regex::new(r#"\.subsystem\s*=\s*"([^"]*)""#)?.captures(content) {
+ fields.insert("subsystem".to_string(), captures.get(1).unwrap().as_str().to_string());
+ }
+
+ // Parse KAPI_PATH (for sysfs path)
+ if let Some(captures) = Regex::new(r#"KAPI_PATH\s*\(\s*"([^"]*)"\s*\)"#)?.captures(content) {
+ fields.insert("sysfs_path".to_string(), captures.get(1).unwrap().as_str().to_string());
+ }
+
+ // Parse KAPI_PERMISSIONS
+ if let Some(captures) = Regex::new(r"KAPI_PERMISSIONS\s*\(\s*(\d+)\s*\)")?.captures(content) {
+ fields.insert("permissions".to_string(), captures.get(1).unwrap().as_str().to_string());
+ }
+
+ // Parse networking-specific fields
+
+ // Parse socket state fields
+ if let Some(captures) = self.socket_state_req_pattern.captures(content) {
+ fields.insert("socket_state_req".to_string(), captures.get(1).unwrap().as_str().to_string());
+ }
+ if let Some(captures) = self.socket_state_result_pattern.captures(content) {
+ fields.insert("socket_state_result".to_string(), captures.get(1).unwrap().as_str().to_string());
+ }
+ if let Some(captures) = self.socket_state_cond_pattern.captures(content) {
+ fields.insert("socket_state_cond".to_string(), captures.get(1).unwrap().as_str().to_string());
+ }
+ if let Some(captures) = self.socket_state_protos_pattern.captures(content) {
+ fields.insert("socket_state_protos".to_string(), captures.get(1).unwrap().as_str().to_string());
+ }
+
+ // Parse protocol behaviors
+ let mut protocol_behaviors = Vec::new();
+ for captures in self.protocol_behavior_pattern.captures_iter(content) {
+ let idx = captures.get(1).unwrap().as_str().parse::<usize>().unwrap_or(0);
+ let protos = captures.get(2).unwrap().as_str();
+ let behavior = captures.get(3).unwrap().as_str()
+ .replace("\"\n\t\t\"", " ")
+ .replace("\"\n\t\"", " ");
+
+ fields.insert(format!("protocol_behavior_{}_protos", idx), protos.to_string());
+ fields.insert(format!("protocol_behavior_{}_desc", idx), behavior);
+ protocol_behaviors.push(idx);
+ }
+ if !protocol_behaviors.is_empty() {
+ fields.insert("protocol_behavior_indices".to_string(),
+ protocol_behaviors.iter().map(ToString::to_string).collect::<Vec<_>>().join(","));
+ }
+
+ // Parse protocol flags (associated with behaviors)
+ for captures in self.protocol_flags_pattern.captures_iter(content) {
+ let idx = captures.get(1).unwrap().as_str().parse::<usize>().unwrap_or(0);
+ let flags = captures.get(2).unwrap().as_str();
+ fields.insert(format!("protocol_behavior_{}_flags", idx), flags.to_string());
+ }
+
+ // Parse address families
+ let mut addr_families = Vec::new();
+ for captures in self.addr_family_pattern.captures_iter(content) {
+ let idx = captures.get(1).unwrap().as_str().parse::<usize>().unwrap_or(0);
+ let family = captures.get(2).unwrap().as_str();
+ let name = captures.get(3).unwrap().as_str();
+ let struct_size = captures.get(4).unwrap().as_str();
+ let min_len = captures.get(5).unwrap().as_str();
+ let max_len = captures.get(6).unwrap().as_str();
+
+ fields.insert(format!("addr_family_{}_id", idx), family.to_string());
+ fields.insert(format!("addr_family_{}_name", idx), name.to_string());
+ fields.insert(format!("addr_family_{}_struct_size", idx), struct_size.to_string());
+ fields.insert(format!("addr_family_{}_min_len", idx), min_len.to_string());
+ fields.insert(format!("addr_family_{}_max_len", idx), max_len.to_string());
+ addr_families.push(idx);
+ }
+ if !addr_families.is_empty() {
+ fields.insert("addr_family_indices".to_string(),
+ addr_families.iter().map(ToString::to_string).collect::<Vec<_>>().join(","));
+ }
+
+ // Parse address family details - these appear after KAPI_ADDR_FAMILY within the block
+ for idx in &addr_families {
+ // Find the KAPI_ADDR_FAMILY block for this index
+ if let Some(family_start) = content.find(&format!("KAPI_ADDR_FAMILY({},", idx)) {
+ if let Some(family_end) = content[family_start..].find("KAPI_ADDR_FAMILY_END") {
+ let family_content = &content[family_start..family_start + family_end];
+
+ // Parse KAPI_ADDR_FORMAT
+ if let Some(captures) = self.addr_format_pattern.captures(family_content) {
+ fields.insert(format!("addr_family_{}_format", idx), captures.get(1).unwrap().as_str().to_string());
+ }
+
+ // Parse KAPI_ADDR_FEATURES
+ if let Some(captures) = self.addr_features_pattern.captures(family_content) {
+ fields.insert(format!("addr_family_{}_wildcard", idx), captures.get(1).unwrap().as_str().to_string());
+ fields.insert(format!("addr_family_{}_multicast", idx), captures.get(2).unwrap().as_str().to_string());
+ fields.insert(format!("addr_family_{}_broadcast", idx), captures.get(3).unwrap().as_str().to_string());
+ }
+
+ // Parse KAPI_ADDR_SPECIAL
+ if let Some(captures) = self.addr_special_pattern.captures(family_content) {
+ let special = captures.get(1).unwrap().as_str()
+ .replace("\"\n\t\t\t \"", " ")
+ .replace("\"\n\t\t\t\"", " ");
+ fields.insert(format!("addr_family_{}_special", idx), special);
+ }
+
+ // Parse KAPI_ADDR_PORTS
+ if let Some(captures) = self.addr_ports_pattern.captures(family_content) {
+ fields.insert(format!("addr_family_{}_port_min", idx), captures.get(1).unwrap().as_str().to_string());
+ fields.insert(format!("addr_family_{}_port_max", idx), captures.get(2).unwrap().as_str().to_string());
+ }
+ }
+ }
+ }
+
+ // Parse KAPI_ADDR_FAMILY_COUNT
+ if let Some(captures) = Regex::new(r"KAPI_ADDR_FAMILY_COUNT\s*\(\s*(\d+)\s*\)")?.captures(content) {
+ fields.insert("addr_family_count".to_string(), captures.get(1).unwrap().as_str().to_string());
+ }
+
+ // Parse KAPI_PROTOCOL_BEHAVIOR_COUNT
+ if let Some(captures) = Regex::new(r"KAPI_PROTOCOL_BEHAVIOR_COUNT\s*\(\s*(\d+)\s*\)")?.captures(content) {
+ fields.insert("protocol_behavior_count".to_string(), captures.get(1).unwrap().as_str().to_string());
+ }
+
+ // Parse buffer spec
+ if let Some(captures) = self.buffer_spec_pattern.captures(content) {
+ fields.insert("buffer_spec_behaviors".to_string(), captures.get(1).unwrap().as_str().to_string());
+ }
+
+ // Parse async spec
+ if let Some(captures) = self.async_spec_pattern.captures(content) {
+ fields.insert("async_spec_modes".to_string(), captures.get(1).unwrap().as_str().to_string());
+ fields.insert("async_spec_errno".to_string(), captures.get(2).unwrap().as_str().to_string());
+ }
+
+ // Parse net data transfer
+ if let Some(captures) = self.net_data_transfer_pattern.captures(content) {
+ fields.insert("net_data_transfer".to_string(), captures.get(1).unwrap().as_str().to_string());
+ }
+
+ // Parse various count fields that appear in networking specs
+ let count_fields = [
+ ("lock_count", r"KAPI_LOCK_COUNT\s*\(\s*(\d+)\s*\)"),
+ ("signal_count", r"KAPI_SIGNAL_COUNT\s*\(\s*(\d+)\s*\)"),
+ ("side_effect_count", r"KAPI_SIDE_EFFECT_COUNT\s*\(\s*(\d+)\s*\)"),
+ ("state_trans_count", r"KAPI_STATE_TRANS_COUNT\s*\(\s*(\d+)\s*\)"),
+ ("constraint_count", r"KAPI_CONSTRAINT_COUNT\s*\(\s*(\d+)\s*\)"),
+ ];
+
+ for (field_name, pattern) in count_fields.iter() {
+ if let Some(captures) = Regex::new(pattern)?.captures(content) {
+ fields.insert((*field_name).to_string(), captures.get(1).unwrap().as_str().to_string());
+ }
+ }
+
+ // Parse state transitions
+ let state_trans_pattern = Regex::new(r#"KAPI_STATE_TRANS\s*\(\s*(\d+)\s*,\s*"([^"]+)"\s*,\s*\n?\s*"([^"]+)"\s*,\s*"([^"]+)"\s*,\s*\n?\s*"([^"]+)"\s*\)(?s).*?KAPI_STATE_TRANS_END"#)?;
+ let state_trans_cond_pattern = Regex::new(r#"KAPI_STATE_TRANS_COND\s*\(\s*"([^"]*)"\s*\)"#)?;
+ let mut state_transitions = Vec::new();
+ for captures in state_trans_pattern.captures_iter(content) {
+ let idx = captures.get(1).unwrap().as_str().parse::<usize>().unwrap_or(0);
+ let object = captures.get(2).unwrap().as_str();
+ let from_state = captures.get(3).unwrap().as_str();
+ let to_state = captures.get(4).unwrap().as_str();
+ let description = captures.get(5).unwrap().as_str();
+ let block = captures.get(0).unwrap().as_str();
+
+ // Parse condition within the state transition block
+ let condition = state_trans_cond_pattern.captures(block)
+ .and_then(|c| c.get(1))
+ .map(|m| m.as_str())
+ .map(ToString::to_string);
+
+ fields.insert(format!("state_trans_{}_object", idx), object.to_string());
+ fields.insert(format!("state_trans_{}_from", idx), from_state.to_string());
+ fields.insert(format!("state_trans_{}_to", idx), to_state.to_string());
+ if let Some(cond) = condition {
+ fields.insert(format!("state_trans_{}_condition", idx), cond);
+ }
+ fields.insert(format!("state_trans_{}_desc", idx), description.to_string());
+ state_transitions.push(idx);
+ }
+
+ if !state_transitions.is_empty() {
+ fields.insert("state_trans_indices".to_string(),
+ state_transitions.iter().map(ToString::to_string).collect::<Vec<_>>().join(","));
+ }
+
+ // Parse side effects
+ let side_effect_pattern = Regex::new(r#"KAPI_SIDE_EFFECT\s*\(\s*(\d+)\s*,\s*([^,]+)\s*,\s*\n?\s*"([^"]+)"\s*,\s*\n?\s*"([^"]+)"\s*\)(?s).*?KAPI_SIDE_EFFECT_END"#)?;
+ let effect_cond_pattern = Regex::new(r#"KAPI_EFFECT_CONDITION\s*\(\s*"([^"]*)"\s*\)"#)?;
+ let effect_reversible_pattern = Regex::new(r"KAPI_EFFECT_REVERSIBLE")?;
+ let mut side_effects = Vec::new();
+ for captures in side_effect_pattern.captures_iter(content) {
+ let idx = captures.get(1).unwrap().as_str().parse::<usize>().unwrap_or(0);
+ let effect_type = captures.get(2).unwrap().as_str().trim();
+ let target = captures.get(3).unwrap().as_str();
+ let description = captures.get(4).unwrap().as_str();
+ let block = captures.get(0).unwrap().as_str();
+
+ // Parse additional fields within the side effect block
+
+ let condition = effect_cond_pattern.captures(block)
+ .and_then(|c| c.get(1))
+ .map(|m| m.as_str())
+ .map(ToString::to_string);
+
+ let reversible = effect_reversible_pattern.is_match(block);
+
+ fields.insert(format!("side_effect_{}_type", idx), effect_type.to_string());
+ fields.insert(format!("side_effect_{}_target", idx), target.to_string());
+ if let Some(cond) = condition {
+ fields.insert(format!("side_effect_{}_condition", idx), cond);
+ }
+ fields.insert(format!("side_effect_{}_desc", idx), description.to_string());
+ fields.insert(format!("side_effect_{}_reversible", idx), reversible.to_string());
+ side_effects.push(idx);
+ }
+
+ if !side_effects.is_empty() {
+ fields.insert("side_effect_indices".to_string(),
+ side_effects.iter().map(ToString::to_string).collect::<Vec<_>>().join(","));
+ }
+
+ // Parse parameters
+ let param_pattern = Regex::new(r#"KAPI_PARAM\s*\(\s*(\d+)\s*,\s*"([^"]+)"\s*,\s*"([^"]+)"\s*,\s*"([^"]+)"\s*\)(?s).*?KAPI_PARAM_END"#)?;
+ let param_flags_pattern = Regex::new(r"KAPI_PARAM_FLAGS\s*\(\s*([^)]+)\s*\)")?;
+ let param_type_pattern = Regex::new(r"KAPI_PARAM_TYPE\s*\(\s*([^)]+)\s*\)")?;
+ let param_constraint_type_pattern = Regex::new(r"KAPI_PARAM_CONSTRAINT_TYPE\s*\(\s*([^)]+)\s*\)")?;
+ let param_constraint_pattern = Regex::new(r#"KAPI_PARAM_CONSTRAINT\s*\(\s*"([^"]*)"\s*\)"#)?;
+ let param_range_pattern = Regex::new(r"KAPI_PARAM_RANGE\s*\(\s*([^,]+)\s*,\s*([^)]+)\s*\)")?;
+ let mut parameters = Vec::new();
+ for captures in param_pattern.captures_iter(content) {
+ let idx = captures.get(1).unwrap().as_str().parse::<usize>().unwrap_or(0);
+ let name = captures.get(2).unwrap().as_str();
+ let type_name = captures.get(3).unwrap().as_str();
+ let description = captures.get(4).unwrap().as_str();
+ let block = captures.get(0).unwrap().as_str();
+
+ // Parse additional fields within the param block
+
+ let flags = param_flags_pattern.captures(block)
+ .and_then(|c| c.get(1))
+ .map_or_else(String::new, |m| m.as_str().to_string());
+
+ let param_type = param_type_pattern.captures(block)
+ .and_then(|c| c.get(1))
+ .map_or_else(String::new, |m| m.as_str().to_string());
+
+ let constraint_type = param_constraint_type_pattern.captures(block)
+ .and_then(|c| c.get(1))
+ .map_or_else(String::new, |m| m.as_str().to_string());
+
+ let constraint = param_constraint_pattern.captures(block)
+ .and_then(|c| c.get(1))
+ .map(|m| m.as_str())
+ .map(ToString::to_string);
+
+ fields.insert(format!("param_{}_name", idx), name.to_string());
+ fields.insert(format!("param_{}_type", idx), type_name.to_string());
+ fields.insert(format!("param_{}_desc", idx), description.to_string());
+ fields.insert(format!("param_{}_flags", idx), flags);
+ fields.insert(format!("param_{}_param_type", idx), param_type);
+ fields.insert(format!("param_{}_constraint_type", idx), constraint_type);
+ if let Some(con) = constraint {
+ fields.insert(format!("param_{}_constraint", idx), con);
+ }
+
+ if let Some(range_caps) = param_range_pattern.captures(block) {
+ fields.insert(format!("param_{}_min", idx), range_caps.get(1).unwrap().as_str().to_string());
+ fields.insert(format!("param_{}_max", idx), range_caps.get(2).unwrap().as_str().to_string());
+ }
+
+ parameters.push(idx);
+ }
+
+ if !parameters.is_empty() {
+ fields.insert("param_indices".to_string(),
+ parameters.iter().map(ToString::to_string).collect::<Vec<_>>().join(","));
+ }
+
+ // Parse return specification
+ let return_pattern = Regex::new(r#"KAPI_RETURN\s*\(\s*"([^"]+)"\s*,\s*"([^"]+)"\s*\)(?s).*?KAPI_RETURN_END"#)?;
+ if let Some(captures) = return_pattern.captures(content) {
+ let type_name = captures.get(1).unwrap().as_str();
+ let description = captures.get(2).unwrap().as_str();
+ let block = captures.get(0).unwrap().as_str();
+
+ fields.insert("return_type".to_string(), type_name.to_string());
+ fields.insert("return_desc".to_string(), description.to_string());
+
+ // Parse additional return fields
+ let ret_type_pattern = Regex::new(r"KAPI_RETURN_TYPE\s*\(\s*([^)]+)\s*\)")?;
+ let check_type_pattern = Regex::new(r"KAPI_RETURN_CHECK_TYPE\s*\(\s*([^)]+)\s*\)")?;
+ let success_pattern = Regex::new(r"KAPI_RETURN_SUCCESS\s*\(\s*([^)]+)\s*\)")?;
+
+ if let Some(caps) = ret_type_pattern.captures(block) {
+ fields.insert("return_return_type".to_string(), caps.get(1).unwrap().as_str().to_string());
+ }
+ if let Some(caps) = check_type_pattern.captures(block) {
+ fields.insert("return_check_type".to_string(), caps.get(1).unwrap().as_str().to_string());
+ }
+ if let Some(caps) = success_pattern.captures(block) {
+ fields.insert("return_success".to_string(), caps.get(1).unwrap().as_str().to_string());
+ }
+ }
+
+ // Parse errors
+ let error_pattern = Regex::new(r#"KAPI_ERROR\s*\(\s*(\d+)\s*,\s*([^,]+)\s*,\s*"([^"]+)"\s*,\s*"([^"]+)"\s*,\s*\n?\s*"([^"]+)"\s*\)"#)?;
+ let mut errors = Vec::new();
+ for captures in error_pattern.captures_iter(content) {
+ let idx = captures.get(1).unwrap().as_str().parse::<usize>().unwrap_or(0);
+ let error_code = captures.get(2).unwrap().as_str();
+ let name = captures.get(3).unwrap().as_str();
+ let condition = captures.get(4).unwrap().as_str();
+ let description = captures.get(5).unwrap().as_str();
+
+ fields.insert(format!("error_{}_code", idx), error_code.to_string());
+ fields.insert(format!("error_{}_name", idx), name.to_string());
+ fields.insert(format!("error_{}_condition", idx), condition.to_string());
+ fields.insert(format!("error_{}_desc", idx), description.to_string());
+ errors.push(idx);
+ }
+
+ if !errors.is_empty() {
+ fields.insert("error_indices".to_string(),
+ errors.iter().map(ToString::to_string).collect::<Vec<_>>().join(","));
+ }
+
+ // Parse locks
+ let lock_pattern = Regex::new(r#"KAPI_LOCK\s*\(\s*(\d+)\s*,\s*"([^"]+)"\s*,\s*([^)]+)\s*\)(?s).*?KAPI_LOCK_END"#)?;
+ let lock_desc_pattern = Regex::new(r#"KAPI_LOCK_DESC\s*\(\s*"([^"]*)"\s*\)"#)?;
+ let mut locks = Vec::new();
+ for captures in lock_pattern.captures_iter(content) {
+ let idx = captures.get(1).unwrap().as_str().parse::<usize>().unwrap_or(0);
+ let lock_name = captures.get(2).unwrap().as_str();
+ let lock_type = captures.get(3).unwrap().as_str();
+ let block = captures.get(0).unwrap().as_str();
+
+ fields.insert(format!("lock_{}_name", idx), lock_name.to_string());
+ fields.insert(format!("lock_{}_type", idx), lock_type.to_string());
+
+ // Parse lock description
+ if let Some(desc_caps) = lock_desc_pattern.captures(block) {
+ fields.insert(format!("lock_{}_desc", idx), desc_caps.get(1).unwrap().as_str().to_string());
+ }
+
+ // Parse lock flags
+ if block.contains("KAPI_LOCK_HELD_ENTRY") {
+ fields.insert(format!("lock_{}_held_entry", idx), "true".to_string());
+ }
+ if block.contains("KAPI_LOCK_HELD_EXIT") {
+ fields.insert(format!("lock_{}_held_exit", idx), "true".to_string());
+ }
+ if block.contains("KAPI_LOCK_ACQUIRED") {
+ fields.insert(format!("lock_{}_acquired", idx), "true".to_string());
+ }
+ if block.contains("KAPI_LOCK_RELEASED") {
+ fields.insert(format!("lock_{}_released", idx), "true".to_string());
+ }
+
+ locks.push(idx);
+ }
+
+ if !locks.is_empty() {
+ fields.insert("lock_indices".to_string(),
+ locks.iter().map(ToString::to_string).collect::<Vec<_>>().join(","));
+ }
+
+ // Parse constraints
+ let constraint_pattern = Regex::new(r#"KAPI_CONSTRAINT\s*\(\s*(\d+)\s*,\s*"([^"]+)"\s*,\s*\n?\s*"([^"]*(?:\s*"[^"]*)*?)"\s*\)(?s).*?KAPI_CONSTRAINT_END"#)?;
+ let constraint_expr_pattern = Regex::new(r#"KAPI_CONSTRAINT_EXPR\s*\(\s*"([^"]*)"\s*\)"#)?;
+ let mut constraints = Vec::new();
+ for captures in constraint_pattern.captures_iter(content) {
+ let idx = captures.get(1).unwrap().as_str().parse::<usize>().unwrap_or(0);
+ let name = captures.get(2).unwrap().as_str();
+ let description = captures.get(3).unwrap().as_str()
+ .replace("\"\n\t\t\t\"", " ")
+ .replace("\"\n\t\t\"", " ")
+ .replace("\"\n\t\"", " ")
+ .trim()
+ .to_string();
+ let block = captures.get(0).unwrap().as_str();
+
+ fields.insert(format!("constraint_{}_name", idx), name.to_string());
+ fields.insert(format!("constraint_{}_desc", idx), description);
+
+ // Parse constraint expression if present
+ if let Some(expr_caps) = constraint_expr_pattern.captures(block) {
+ fields.insert(format!("constraint_{}_expr", idx), expr_caps.get(1).unwrap().as_str().to_string());
+ }
+
+ constraints.push(idx);
+ }
+
+ if !constraints.is_empty() {
+ fields.insert("constraint_indices".to_string(),
+ constraints.iter().map(ToString::to_string).collect::<Vec<_>>().join(","));
+ }
+
+ Ok(())
+ }
+
+ /// Scan a directory tree for files containing KAPI specifications
+ pub fn scan_directory(&self, dir: &Path, extensions: &[&str]) -> Result<Vec<SourceApiSpec>> {
+ let mut all_specs = Vec::new();
+
+ for entry in WalkDir::new(dir)
+ .follow_links(true)
+ .into_iter()
+ .filter_map(Result::ok)
+ {
+ let path = entry.path();
+
+ // Skip non-files
+ if !path.is_file() {
+ continue;
+ }
+
+ // Check file extension
+ if let Some(ext) = path.extension() {
+ if extensions.iter().any(|&e| ext == e) {
+ // Try to parse the file
+ match self.parse_file(path) {
+ Ok(specs) => {
+ if !specs.is_empty() {
+ all_specs.extend(specs);
+ }
+ }
+ Err(_e) => {}
+ }
+ }
+ }
+ }
+
+ Ok(all_specs)
+ }
+
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+ use std::io::Write;
+ use tempfile::NamedTempFile;
+
+ #[test]
+ fn test_parse_syscall_spec() {
+ let parser = SourceParser::new().unwrap();
+
+ let content = r#"
+DEFINE_KERNEL_API_SPEC(sys_mlock)
+ KAPI_DESCRIPTION("Lock pages in memory")
+ KAPI_LONG_DESC("Locks pages in the specified address range into RAM")
+ KAPI_CONTEXT(KAPI_CTX_PROCESS | KAPI_CTX_SLEEPABLE)
+
+ KAPI_PARAM(0, "start", "unsigned long", "Starting address")
+ KAPI_PARAM_END
+
+ KAPI_PARAM(1, "len", "size_t", "Length of range")
+ KAPI_PARAM_END
+
+ .param_count = 2,
+ .error_count = 3,
+
+KAPI_END_SPEC
+"#;
+
+ let mut temp_file = NamedTempFile::new().unwrap();
+ write!(temp_file, "{}", content).unwrap();
+
+ let specs = parser.parse_content(content, temp_file.path()).unwrap();
+
+ assert_eq!(specs.len(), 1);
+ assert_eq!(specs[0].name, "sys_mlock");
+ assert_eq!(specs[0].api_type, ApiType::Syscall);
+ assert_eq!(specs[0].parsed_fields.get("description").unwrap(), "Lock pages in memory");
+ assert_eq!(specs[0].parsed_fields.get("param_count").unwrap(), "2");
+ }
+
+ #[test]
+ fn test_parse_ioctl_spec() {
+ let parser = SourceParser::new().unwrap();
+
+ let content = r#"
+DEFINE_IOCTL_API_SPEC(binder_write_read, BINDER_WRITE_READ, "BINDER_WRITE_READ")
+ KAPI_DESCRIPTION("Perform read/write operations on binder")
+ KAPI_CONTEXT(KAPI_CTX_PROCESS | KAPI_CTX_SLEEPABLE)
+
+ KAPI_PARAM(0, "write_size", "binder_size_t", "Bytes to write")
+ KAPI_PARAM_END
+
+KAPI_END_IOCTL_SPEC
+"#;
+
+ let mut temp_file = NamedTempFile::new().unwrap();
+ write!(temp_file, "{}", content).unwrap();
+
+ let specs = parser.parse_content(content, temp_file.path()).unwrap();
+
+ assert_eq!(specs.len(), 1);
+ assert_eq!(specs[0].name, "binder_write_read");
+ assert_eq!(specs[0].api_type, ApiType::Ioctl);
+ assert_eq!(specs[0].parsed_fields.get("cmd_name").unwrap(), "BINDER_WRITE_READ");
+ }
+
+ #[test]
+ fn test_parse_sysfs_spec() {
+ let parser = SourceParser::new().unwrap();
+
+ let content = r#"
+DEFINE_SYSFS_API_SPEC(nr_requests)
+ KAPI_DESCRIPTION("Number of allocatable requests")
+ KAPI_LONG_DESC("This controls how many requests may be allocated")
+ KAPI_SUBSYSTEM("block")
+ KAPI_PATH("/sys/block/<disk>/queue/nr_requests")
+ KAPI_PERMISSIONS(0644)
+ .param_count = 1,
+KAPI_END_SPEC
+"#;
+
+ let mut temp_file = NamedTempFile::new().unwrap();
+ write!(temp_file, "{}", content).unwrap();
+
+ let specs = parser.parse_content(content, temp_file.path()).unwrap();
+
+ assert_eq!(specs.len(), 1);
+ assert_eq!(specs[0].name, "nr_requests");
+ assert_eq!(specs[0].api_type, ApiType::Sysfs);
+ assert_eq!(specs[0].parsed_fields.get("description").unwrap(), "Number of allocatable requests");
+ assert_eq!(specs[0].parsed_fields.get("subsystem").unwrap(), "block");
+ assert_eq!(specs[0].parsed_fields.get("sysfs_path").unwrap(), "/sys/block/<disk>/queue/nr_requests");
+ assert_eq!(specs[0].parsed_fields.get("permissions").unwrap(), "0644");
+ }
+}
+
+// SourceExtractor implementation
+pub struct SourceExtractor {
+ specs: Vec<SourceApiSpec>,
+}
+
+impl SourceExtractor {
+ pub fn new(path: &str) -> Result<Self> {
+ let parser = SourceParser::new()?;
+ let path_obj = Path::new(&path);
+
+ let specs = if path_obj.is_file() {
+ parser.parse_file(path_obj)?
+ } else if path_obj.is_dir() {
+ parser.scan_directory(path_obj, &["c", "h"])?
+ } else {
+ anyhow::bail!("Path does not exist: {}", path_obj.display())
+ };
+
+ Ok(SourceExtractor { specs })
+ }
+
+ fn convert_capability_action(action: &str) -> String {
+ match action {
+ "KAPI_CAP_BYPASS_CHECK" => "Bypasses check".to_string(),
+ "KAPI_CAP_INCREASE_LIMIT" => "Increases limit".to_string(),
+ "KAPI_CAP_OVERRIDE_RESTRICTION" => "Overrides restriction".to_string(),
+ "KAPI_CAP_GRANT_PERMISSION" => "Grants permission".to_string(),
+ "KAPI_CAP_MODIFY_BEHAVIOR" => "Modifies behavior".to_string(),
+ "KAPI_CAP_ACCESS_RESOURCE" => "Allows resource access".to_string(),
+ "KAPI_CAP_PERFORM_OPERATION" => "Allows operation".to_string(),
+ _ => action.to_string(),
+ }
+ }
+
+ fn parse_state_transitions(source_spec: &SourceApiSpec) -> Vec<StateTransitionSpec> {
+ let mut transitions = Vec::new();
+
+ if let Some(indices_str) = source_spec.parsed_fields.get("state_trans_indices") {
+ for idx_str in indices_str.split(',') {
+ if let Ok(idx) = idx_str.parse::<usize>() {
+ let object = source_spec.parsed_fields.get(&format!("state_trans_{}_object", idx))
+ .cloned()
+ .unwrap_or_else(String::new);
+ let from_state = source_spec.parsed_fields.get(&format!("state_trans_{}_from", idx))
+ .cloned()
+ .unwrap_or_else(|| "any".to_string());
+ let to_state = source_spec.parsed_fields.get(&format!("state_trans_{}_to", idx))
+ .cloned()
+ .unwrap_or_else(|| "changed".to_string());
+ let condition = source_spec.parsed_fields.get(&format!("state_trans_{}_condition", idx))
+ .cloned();
+ let description = source_spec.parsed_fields.get(&format!("state_trans_{}_desc", idx))
+ .cloned()
+ .unwrap_or_else(String::new);
+
+ transitions.push(StateTransitionSpec {
+ object,
+ from_state,
+ to_state,
+ condition,
+ description,
+ });
+ }
+ }
+ }
+
+ transitions
+ }
+
+ fn parse_side_effects(source_spec: &SourceApiSpec) -> Vec<SideEffectSpec> {
+ let mut effects = Vec::new();
+
+ if let Some(indices_str) = source_spec.parsed_fields.get("side_effect_indices") {
+ for idx_str in indices_str.split(',') {
+ if let Ok(idx) = idx_str.parse::<usize>() {
+ let effect_type_str = source_spec.parsed_fields.get(&format!("side_effect_{}_type", idx))
+ .cloned()
+ .unwrap_or_else(String::new);
+ let target = source_spec.parsed_fields.get(&format!("side_effect_{}_target", idx))
+ .cloned()
+ .unwrap_or_else(String::new);
+ let condition = source_spec.parsed_fields.get(&format!("side_effect_{}_condition", idx))
+ .cloned();
+ let description = source_spec.parsed_fields.get(&format!("side_effect_{}_desc", idx))
+ .cloned()
+ .unwrap_or_else(String::new);
+ let reversible = source_spec.parsed_fields.get(&format!("side_effect_{}_reversible", idx))
+ .is_some_and(|s| s == "true");
+
+ // Convert effect type string to u32
+ let effect_type = Self::parse_effect_type(&effect_type_str);
+
+ effects.push(SideEffectSpec {
+ effect_type,
+ target,
+ condition,
+ description,
+ reversible,
+ });
+ }
+ }
+ }
+
+ effects
+ }
+
+ fn parse_effect_type(effect_type_str: &str) -> u32 {
+ // Parse effect type flags
+ let mut effect_type = 0u32;
+ let parts: Vec<&str> = effect_type_str.split('|').map(str::trim).collect();
+
+ for part in parts {
+ match part {
+ "KAPI_EFFECT_MODIFY_STATE" => effect_type |= 1 << 0,
+ "KAPI_EFFECT_ALLOCATE_MEMORY" => effect_type |= 1 << 1,
+ "KAPI_EFFECT_FREE_MEMORY" => effect_type |= 1 << 2,
+ "KAPI_EFFECT_IO_OPERATION" => effect_type |= 1 << 3,
+ "KAPI_EFFECT_SIGNAL_SEND" => effect_type |= 1 << 4,
+ "KAPI_EFFECT_PROCESS_CREATE" => effect_type |= 1 << 5,
+ "KAPI_EFFECT_PROCESS_TERMINATE" => effect_type |= 1 << 6,
+ "KAPI_EFFECT_FILE_CREATE" => effect_type |= 1 << 7,
+ "KAPI_EFFECT_FILE_DELETE" => effect_type |= 1 << 8,
+ "KAPI_EFFECT_RESOURCE_CREATE" => effect_type |= 1 << 9,
+ "KAPI_EFFECT_RESOURCE_DESTROY" => effect_type |= 1 << 10,
+ "KAPI_EFFECT_LOCK_ACQUIRE" => effect_type |= 1 << 11,
+ "KAPI_EFFECT_LOCK_RELEASE" => effect_type |= 1 << 12,
+ "KAPI_EFFECT_NETWORK_IO" => effect_type |= 1 << 13,
+ "KAPI_EFFECT_SYSTEM_STATE" => effect_type |= 1 << 14,
+ _ => {} // Unknown effect type
+ }
+ }
+
+ effect_type
+ }
+
+ fn parse_parameters(source_spec: &SourceApiSpec) -> Vec<ParamSpec> {
+ let mut params = Vec::new();
+
+ if let Some(indices_str) = source_spec.parsed_fields.get("param_indices") {
+ for idx_str in indices_str.split(',') {
+ if let Ok(idx) = idx_str.parse::<u32>() {
+ let name = source_spec.parsed_fields.get(&format!("param_{}_name", idx))
+ .cloned()
+ .unwrap_or_else(String::new);
+ let type_name = source_spec.parsed_fields.get(&format!("param_{}_type", idx))
+ .cloned()
+ .unwrap_or_else(String::new);
+ let description = source_spec.parsed_fields.get(&format!("param_{}_desc", idx))
+ .cloned()
+ .unwrap_or_else(String::new);
+ let flags_str = source_spec.parsed_fields.get(&format!("param_{}_flags", idx))
+ .cloned()
+ .unwrap_or_else(String::new);
+ let param_type_str = source_spec.parsed_fields.get(&format!("param_{}_param_type", idx))
+ .cloned()
+ .unwrap_or_else(String::new);
+ let constraint_type_str = source_spec.parsed_fields.get(&format!("param_{}_constraint_type", idx))
+ .cloned()
+ .unwrap_or_else(String::new);
+ let constraint = source_spec.parsed_fields.get(&format!("param_{}_constraint", idx))
+ .cloned();
+ let min_value = source_spec.parsed_fields.get(&format!("param_{}_min", idx))
+ .and_then(|s| s.parse::<i64>().ok());
+ let max_value = source_spec.parsed_fields.get(&format!("param_{}_max", idx))
+ .and_then(|s| s.parse::<i64>().ok());
+
+ params.push(ParamSpec {
+ index: idx,
+ name,
+ type_name,
+ description,
+ flags: Self::parse_param_flags(&flags_str),
+ param_type: Self::parse_param_type(¶m_type_str),
+ constraint_type: Self::parse_constraint_type(&constraint_type_str),
+ constraint,
+ min_value,
+ max_value,
+ valid_mask: None,
+ enum_values: Vec::new(),
+ size: None,
+ alignment: None,
+ });
+ }
+ }
+ }
+
+ params
+ }
+
+ fn parse_return_spec(source_spec: &SourceApiSpec) -> Option<ReturnSpec> {
+ if let (Some(type_name), Some(description)) = (
+ source_spec.parsed_fields.get("return_type"),
+ source_spec.parsed_fields.get("return_desc")
+ ) {
+ let return_type_str = source_spec.parsed_fields.get("return_return_type")
+ .cloned()
+ .unwrap_or_else(String::new);
+ let check_type_str = source_spec.parsed_fields.get("return_check_type")
+ .cloned()
+ .unwrap_or_else(String::new);
+ let success_value = source_spec.parsed_fields.get("return_success")
+ .and_then(|s| s.parse::<i64>().ok());
+
+ Some(ReturnSpec {
+ type_name: type_name.clone(),
+ description: description.clone(),
+ return_type: Self::parse_return_type(&return_type_str),
+ check_type: Self::parse_check_type(&check_type_str),
+ success_value,
+ success_min: None,
+ success_max: None,
+ error_values: Vec::new(),
+ })
+ } else {
+ None
+ }
+ }
+
+ fn parse_errors(source_spec: &SourceApiSpec) -> Vec<ErrorSpec> {
+ let mut errors = Vec::new();
+
+ if let Some(indices_str) = source_spec.parsed_fields.get("error_indices") {
+ for idx_str in indices_str.split(',') {
+ if let Ok(idx) = idx_str.parse::<usize>() {
+ let error_code_str = source_spec.parsed_fields.get(&format!("error_{}_code", idx))
+ .cloned()
+ .unwrap_or_else(String::new);
+ let name = source_spec.parsed_fields.get(&format!("error_{}_name", idx))
+ .cloned()
+ .unwrap_or_else(String::new);
+ let condition = source_spec.parsed_fields.get(&format!("error_{}_condition", idx))
+ .cloned()
+ .unwrap_or_else(String::new);
+ let description = source_spec.parsed_fields.get(&format!("error_{}_desc", idx))
+ .cloned()
+ .unwrap_or_else(String::new);
+
+ // Parse error code (handle -EINVAL format)
+ let error_code = if error_code_str.starts_with("-E") {
+ // Map common error codes
+ match error_code_str.as_str() {
+ "-EINVAL" => -22,
+ "-ENOMEM" => -12,
+ "-EBUSY" => -16,
+ "-ENODEV" => -19,
+ "-ENOENT" => -2,
+ "-EPERM" => -1,
+ "-EACCES" => -13,
+ "-EFAULT" => -14,
+ "-EAGAIN" => -11,
+ "-EEXIST" => -17,
+ _ => 0,
+ }
+ } else {
+ error_code_str.parse::<i32>().unwrap_or(0)
+ };
+
+ errors.push(ErrorSpec {
+ error_code,
+ name,
+ condition,
+ description,
+ });
+ }
+ }
+ }
+
+ errors
+ }
+
+ fn parse_locks(source_spec: &SourceApiSpec) -> Vec<LockSpec> {
+ let mut locks = Vec::new();
+
+ if let Some(indices_str) = source_spec.parsed_fields.get("lock_indices") {
+ for idx_str in indices_str.split(',') {
+ if let Ok(idx) = idx_str.parse::<usize>() {
+ let lock_name = source_spec.parsed_fields.get(&format!("lock_{}_name", idx))
+ .cloned()
+ .unwrap_or_else(String::new);
+ let lock_type_str = source_spec.parsed_fields.get(&format!("lock_{}_type", idx))
+ .cloned()
+ .unwrap_or_else(String::new);
+ let description = source_spec.parsed_fields.get(&format!("lock_{}_desc", idx))
+ .cloned()
+ .unwrap_or_else(String::new);
+ let held_on_entry = source_spec.parsed_fields.get(&format!("lock_{}_held_entry", idx))
+ .is_some_and(|s| s == "true");
+ let held_on_exit = source_spec.parsed_fields.get(&format!("lock_{}_held_exit", idx))
+ .is_some_and(|s| s == "true");
+ let acquired = source_spec.parsed_fields.get(&format!("lock_{}_acquired", idx))
+ .is_some_and(|s| s == "true");
+ let released = source_spec.parsed_fields.get(&format!("lock_{}_released", idx))
+ .is_some_and(|s| s == "true");
+
+ locks.push(LockSpec {
+ lock_name,
+ lock_type: Self::parse_lock_type(&lock_type_str),
+ acquired,
+ released,
+ held_on_entry,
+ held_on_exit,
+ description,
+ });
+ }
+ }
+ }
+
+ locks
+ }
+
+ fn parse_constraints(source_spec: &SourceApiSpec) -> Vec<ConstraintSpec> {
+ let mut constraints = Vec::new();
+
+ if let Some(indices_str) = source_spec.parsed_fields.get("constraint_indices") {
+ for idx_str in indices_str.split(',') {
+ if let Ok(idx) = idx_str.parse::<usize>() {
+ let name = source_spec.parsed_fields.get(&format!("constraint_{}_name", idx))
+ .cloned()
+ .unwrap_or_else(String::new);
+ let description = source_spec.parsed_fields.get(&format!("constraint_{}_desc", idx))
+ .cloned()
+ .unwrap_or_else(String::new);
+ let expression = source_spec.parsed_fields.get(&format!("constraint_{}_expr", idx))
+ .cloned();
+
+ constraints.push(ConstraintSpec {
+ name,
+ description,
+ expression,
+ });
+ }
+ }
+ }
+
+ constraints
+ }
+
+ fn parse_param_flags(flags_str: &str) -> u32 {
+ let mut flags = 0u32;
+ let parts: Vec<&str> = flags_str.split('|').map(str::trim).collect();
+
+ for part in parts {
+ match part {
+ "KAPI_PARAM_IN" => flags |= 1 << 0,
+ "KAPI_PARAM_OUT" => flags |= 1 << 1,
+ "KAPI_PARAM_INOUT" => flags |= (1 << 0) | (1 << 1),
+ "KAPI_PARAM_USER" => flags |= 1 << 2,
+ "KAPI_PARAM_OPTIONAL" => flags |= 1 << 3,
+ _ => {}
+ }
+ }
+
+ flags
+ }
+
+ fn parse_param_type(type_str: &str) -> u32 {
+ match type_str.trim() {
+ "KAPI_TYPE_INT" => 1,
+ "KAPI_TYPE_UINT" => 2,
+ "KAPI_TYPE_PTR" => 3,
+ "KAPI_TYPE_STRUCT" => 4,
+ "KAPI_TYPE_ENUM" => 5,
+ "KAPI_TYPE_FLAGS" => 6,
+ "KAPI_TYPE_FD" => 7,
+ "KAPI_TYPE_STRING" => 8,
+ _ => 0,
+ }
+ }
+
+ fn parse_constraint_type(type_str: &str) -> u32 {
+ match type_str.trim() {
+ "KAPI_CONSTRAINT_RANGE" => 1,
+ "KAPI_CONSTRAINT_MASK" => 2,
+ "KAPI_CONSTRAINT_ENUM" => 3,
+ "KAPI_CONSTRAINT_SIZE" => 4,
+ "KAPI_CONSTRAINT_ALIGNMENT" => 5,
+ _ => 0, // Default to NONE (includes "KAPI_CONSTRAINT_NONE")
+ }
+ }
+
+ fn parse_return_type(type_str: &str) -> u32 {
+ match type_str.trim() {
+ "KAPI_TYPE_INT" => 1,
+ "KAPI_TYPE_UINT" => 2,
+ "KAPI_TYPE_PTR" => 3,
+ "KAPI_TYPE_FD" => 7,
+ _ => 0,
+ }
+ }
+
+ fn parse_check_type(type_str: &str) -> u32 {
+ match type_str.trim() {
+ "KAPI_RETURN_SUCCESS_CHECK" => 1,
+ "KAPI_RETURN_ERROR_CHECK" => 2,
+ "KAPI_RETURN_RANGE_CHECK" => 3,
+ "KAPI_RETURN_PTR_CHECK" => 4,
+ _ => 0,
+ }
+ }
+
+ fn parse_lock_type(type_str: &str) -> u32 {
+ match type_str.trim() {
+ "KAPI_LOCK_MUTEX" => 1,
+ "KAPI_LOCK_SPINLOCK" => 2,
+ "KAPI_LOCK_RWLOCK" => 3,
+ "KAPI_LOCK_SEMAPHORE" => 4,
+ "KAPI_LOCK_RCU" => 5,
+ _ => 0,
+ }
+ }
+
+ fn parse_context_flags(flags_str: &str) -> Vec<String> {
+ let mut result = Vec::new();
+ let parts: Vec<&str> = flags_str.split('|').map(str::trim).collect();
+
+ for part in parts {
+ match part {
+ "KAPI_CTX_PROCESS" => result.push("Process context".to_string()),
+ "KAPI_CTX_SOFTIRQ" => result.push("Softirq context".to_string()),
+ "KAPI_CTX_HARDIRQ" => result.push("Hardirq context".to_string()),
+ "KAPI_CTX_NMI" => result.push("NMI context".to_string()),
+ "KAPI_CTX_USER" => result.push("User mode".to_string()),
+ "KAPI_CTX_KERNEL" => result.push("Kernel mode".to_string()),
+ "KAPI_CTX_SLEEPABLE" => result.push("May sleep".to_string()),
+ "KAPI_CTX_ATOMIC" => result.push("Atomic context".to_string()),
+ "KAPI_CTX_PREEMPTIBLE" => result.push("Preemptible".to_string()),
+ "KAPI_CTX_MIGRATION_DISABLED" => result.push("Migration disabled".to_string()),
+ _ => {} // Ignore unknown flags
+ }
+ }
+
+ result
+ }
+
+ fn convert_to_api_spec(&self, source_spec: &SourceApiSpec) -> ApiSpec {
+ let mut capabilities = Vec::new();
+
+ // Extract capabilities
+ if let Some(cap_count_str) = source_spec.parsed_fields.get("capability_count") {
+ if let Ok(cap_count) = cap_count_str.parse::<usize>() {
+ for i in 0..cap_count {
+ let cap_key = format!("capability_{}", i);
+
+ if let (Some(id_str), Some(name), Some(action)) = (
+ source_spec.parsed_fields.get(&format!("{}_id", cap_key)),
+ source_spec.parsed_fields.get(&format!("{}_name", cap_key)),
+ source_spec.parsed_fields.get(&format!("{}_action", cap_key))
+ ) {
+ let cap_id = id_str.parse::<i32>().unwrap_or(0);
+ capabilities.push(CapabilitySpec {
+ capability: cap_id,
+ name: name.clone(),
+ action: Self::convert_capability_action(action),
+ allows: source_spec.parsed_fields.get(&format!("{}_allows", cap_key))
+ .cloned()
+ .unwrap_or_else(String::new),
+ without_cap: source_spec.parsed_fields.get(&format!("{}_without", cap_key))
+ .cloned()
+ .unwrap_or_else(String::new),
+ check_condition: source_spec.parsed_fields.get(&format!("{}_condition", cap_key))
+ .cloned(),
+ priority: source_spec.parsed_fields.get(&format!("{}_priority", cap_key))
+ .and_then(|s| s.parse::<u8>().ok()),
+ alternatives: Vec::new(), // Not parsed from source yet
+ });
+ }
+ }
+ }
+ }
+
+ // Parse socket state
+ let socket_state = if source_spec.parsed_fields.contains_key("socket_state_req") ||
+ source_spec.parsed_fields.contains_key("socket_state_result") {
+ Some(SocketStateSpec {
+ required_states: source_spec.parsed_fields.get("socket_state_req")
+ .map(|s| vec![s.clone()])
+ .unwrap_or_default(),
+ forbidden_states: Vec::new(), // Not parsed yet
+ resulting_state: source_spec.parsed_fields.get("socket_state_result").cloned(),
+ condition: source_spec.parsed_fields.get("socket_state_cond").cloned(),
+ applicable_protocols: source_spec.parsed_fields.get("socket_state_protos").cloned(),
+ })
+ } else {
+ None
+ };
+
+ // Parse protocol behaviors
+ let mut protocol_behaviors = Vec::new();
+ if let Some(indices_str) = source_spec.parsed_fields.get("protocol_behavior_indices") {
+ for idx_str in indices_str.split(',') {
+ if let Ok(idx) = idx_str.parse::<usize>() {
+ if let (Some(protos), Some(desc)) = (
+ source_spec.parsed_fields.get(&format!("protocol_behavior_{}_protos", idx)),
+ source_spec.parsed_fields.get(&format!("protocol_behavior_{}_desc", idx))
+ ) {
+ protocol_behaviors.push(ProtocolBehaviorSpec {
+ applicable_protocols: protos.clone(),
+ behavior: desc.clone(),
+ protocol_flags: source_spec.parsed_fields.get(&format!("protocol_behavior_{}_flags", idx)).cloned(),
+ flag_description: None, // Could be enhanced to parse flag descriptions
+ });
+ }
+ }
+ }
+ }
+
+ // Parse address families
+ let mut addr_families = Vec::new();
+ if let Some(indices_str) = source_spec.parsed_fields.get("addr_family_indices") {
+ for idx_str in indices_str.split(',') {
+ if let Ok(idx) = idx_str.parse::<usize>() {
+ if let (Some(family_str), Some(name), Some(struct_size_str), Some(min_len_str), Some(max_len_str)) = (
+ source_spec.parsed_fields.get(&format!("addr_family_{}_id", idx)),
+ source_spec.parsed_fields.get(&format!("addr_family_{}_name", idx)),
+ source_spec.parsed_fields.get(&format!("addr_family_{}_struct_size", idx)),
+ source_spec.parsed_fields.get(&format!("addr_family_{}_min_len", idx)),
+ source_spec.parsed_fields.get(&format!("addr_family_{}_max_len", idx))
+ ) {
+ // Parse AF_INET etc as integers
+ let family = if family_str.starts_with("AF_") {
+ // This is a constant name, we'd need to map it to the actual value
+ // For now, use a placeholder
+ match family_str.as_str() {
+ "AF_UNIX" => 1,
+ "AF_INET" => 2,
+ "AF_INET6" => 10,
+ "AF_NETLINK" => 16,
+ "AF_PACKET" => 17,
+ "AF_BLUETOOTH" => 31,
+ _ => 0,
+ }
+ } else {
+ family_str.parse::<i32>().unwrap_or(0)
+ };
+
+ // For sizeof() expressions, we'll store the string as-is
+ let struct_size = if struct_size_str.starts_with("sizeof(") {
+ // Map common struct sizes - this is a limitation of static parsing
+ match struct_size_str.as_str() {
+ "sizeof(struct sockaddr_un)" => 110,
+ "sizeof(struct sockaddr_in)" => 16,
+ "sizeof(struct sockaddr_in6)" => 28,
+ "sizeof(struct sockaddr_nl)" => 12,
+ "sizeof(struct sockaddr_ll)" => 20,
+ "sizeof(struct sockaddr)" => 16, // generic sockaddr
+ _ => 0,
+ }
+ } else {
+ struct_size_str.parse::<usize>().unwrap_or(0)
+ };
+
+ addr_families.push(AddrFamilySpec {
+ family,
+ family_name: name.clone(),
+ addr_struct_size: struct_size,
+ min_addr_len: min_len_str.parse::<usize>().unwrap_or(0),
+ max_addr_len: max_len_str.parse::<usize>().unwrap_or(0),
+ addr_format: source_spec.parsed_fields.get(&format!("addr_family_{}_format", idx)).cloned(),
+ supports_wildcard: source_spec.parsed_fields.get(&format!("addr_family_{}_wildcard", idx))
+ .is_some_and(|s| s == "true"),
+ supports_multicast: source_spec.parsed_fields.get(&format!("addr_family_{}_multicast", idx))
+ .is_some_and(|s| s == "true"),
+ supports_broadcast: source_spec.parsed_fields.get(&format!("addr_family_{}_broadcast", idx))
+ .is_some_and(|s| s == "true"),
+ special_addresses: source_spec.parsed_fields.get(&format!("addr_family_{}_special", idx)).cloned(),
+ port_range_min: source_spec.parsed_fields.get(&format!("addr_family_{}_port_min", idx))
+ .and_then(|s| s.parse::<u32>().ok()).unwrap_or(0),
+ port_range_max: source_spec.parsed_fields.get(&format!("addr_family_{}_port_max", idx))
+ .and_then(|s| s.parse::<u32>().ok()).unwrap_or(0),
+ });
+ }
+ }
+ }
+ }
+
+ // Parse buffer spec
+ let buffer_spec = if source_spec.parsed_fields.contains_key("buffer_spec_behaviors") {
+ Some(BufferSpec {
+ buffer_behaviors: source_spec.parsed_fields.get("buffer_spec_behaviors").cloned(),
+ min_buffer_size: None,
+ max_buffer_size: None,
+ optimal_buffer_size: None,
+ })
+ } else {
+ None
+ };
+
+ // Parse async spec
+ let async_spec = if source_spec.parsed_fields.contains_key("async_spec_modes") {
+ Some(AsyncSpec {
+ supported_modes: source_spec.parsed_fields.get("async_spec_modes").cloned(),
+ nonblock_errno: source_spec.parsed_fields.get("async_spec_errno")
+ .and_then(|s| s.parse::<i32>().ok()),
+ })
+ } else {
+ None
+ };
+
+ ApiSpec {
+ name: source_spec.name.clone(),
+ api_type: match source_spec.api_type {
+ ApiType::Syscall => "syscall".to_string(),
+ ApiType::Ioctl => "ioctl".to_string(),
+ ApiType::Function => "function".to_string(),
+ ApiType::Sysfs => "sysfs".to_string(),
+ ApiType::Unknown => "unknown".to_string(),
+ },
+ description: source_spec.parsed_fields.get("description").cloned(),
+ long_description: source_spec.parsed_fields.get("long_description").cloned(),
+ version: source_spec.parsed_fields.get("version").cloned(),
+ context_flags: source_spec.parsed_fields.get("context")
+ .map(|c| Self::parse_context_flags(c))
+ .unwrap_or_default(),
+ param_count: source_spec.parsed_fields.get("param_count")
+ .and_then(|s| s.parse::<u32>().ok()),
+ error_count: source_spec.parsed_fields.get("error_count")
+ .and_then(|s| s.parse::<u32>().ok()),
+ examples: source_spec.parsed_fields.get("examples").cloned(),
+ notes: source_spec.parsed_fields.get("notes").cloned(),
+ since_version: source_spec.parsed_fields.get("since_version").cloned(),
+ // Sysfs-specific fields
+ subsystem: source_spec.parsed_fields.get("subsystem").cloned(),
+ sysfs_path: source_spec.parsed_fields.get("sysfs_path").cloned(),
+ permissions: source_spec.parsed_fields.get("permissions").cloned(),
+ // Networking-specific fields
+ socket_state,
+ protocol_behaviors,
+ addr_families,
+ buffer_spec,
+ async_spec,
+ net_data_transfer: source_spec.parsed_fields.get("net_data_transfer").cloned(),
+ capabilities,
+ parameters: Self::parse_parameters(source_spec),
+ return_spec: Self::parse_return_spec(source_spec),
+ errors: Self::parse_errors(source_spec),
+ signals: vec![],
+ signal_masks: vec![],
+ side_effects: Self::parse_side_effects(source_spec),
+ state_transitions: Self::parse_state_transitions(source_spec),
+ constraints: Self::parse_constraints(source_spec),
+ locks: Self::parse_locks(source_spec),
+ }
+ }
+}
+
+impl ApiExtractor for SourceExtractor {
+ fn extract_all(&self) -> Result<Vec<ApiSpec>> {
+ Ok(self.specs.iter()
+ .map(|s| self.convert_to_api_spec(s))
+ .collect())
+ }
+
+ fn extract_by_name(&self, name: &str) -> Result<Option<ApiSpec>> {
+ Ok(self.specs.iter()
+ .find(|s| s.name == name)
+ .map(|s| self.convert_to_api_spec(s)))
+ }
+
+ fn display_api_details(
+ &self,
+ api_name: &str,
+ formatter: &mut dyn OutputFormatter,
+ writer: &mut dyn Write,
+ ) -> Result<()> {
+ if let Some(spec) = self.specs.iter().find(|s| s.name == api_name) {
+ let api_spec = self.convert_to_api_spec(spec);
+ display_api_spec(&api_spec, formatter, writer)?;
+ }
+ Ok(())
+ }
+}
\ No newline at end of file
diff --git a/tools/kapi/src/extractor/vmlinux/binary_utils.rs b/tools/kapi/src/extractor/vmlinux/binary_utils.rs
new file mode 100644
index 0000000000000..e3f5d1e939d86
--- /dev/null
+++ b/tools/kapi/src/extractor/vmlinux/binary_utils.rs
@@ -0,0 +1,283 @@
+
+// Constants for all structure field sizes
+pub mod sizes {
+ pub const NAME: usize = 128;
+ pub const DESC: usize = 512;
+ pub const MAX_PARAMS: usize = 16;
+ pub const MAX_ERRORS: usize = 32;
+ pub const MAX_CONSTRAINTS: usize = 16;
+ pub const MAX_CAPABILITIES: usize = 8;
+ pub const MAX_SIGNALS: usize = 16;
+ pub const MAX_STRUCT_SPECS: usize = 8;
+ pub const MAX_SIDE_EFFECTS: usize = 16;
+ pub const MAX_STATE_TRANS: usize = 16;
+}
+
+// Helper for reading data at specific offsets
+pub struct DataReader<'a> {
+ data: &'a [u8],
+ pos: usize,
+}
+
+impl<'a> DataReader<'a> {
+ pub fn new(data: &'a [u8], offset: usize) -> Self {
+ Self { data, pos: offset }
+ }
+
+ pub fn read_bytes(&mut self, len: usize) -> Option<&'a [u8]> {
+ if self.pos + len <= self.data.len() {
+ let bytes = &self.data[self.pos..self.pos + len];
+ self.pos += len;
+ Some(bytes)
+ } else {
+ None
+ }
+ }
+
+ pub fn read_cstring(&mut self, max_len: usize) -> Option<String> {
+ let bytes = self.read_bytes(max_len)?;
+ if let Some(null_pos) = bytes.iter().position(|&b| b == 0) {
+ if null_pos > 0 {
+ if let Ok(s) = std::str::from_utf8(&bytes[..null_pos]) {
+ return Some(s.to_string());
+ }
+ }
+ }
+ None
+ }
+
+ pub fn read_u32(&mut self) -> Option<u32> {
+ let bytes = self.read_bytes(4)?;
+ Some(u32::from_le_bytes([bytes[0], bytes[1], bytes[2], bytes[3]]))
+ }
+
+ pub fn read_u8(&mut self) -> Option<u8> {
+ let bytes = self.read_bytes(1)?;
+ Some(bytes[0])
+ }
+
+ pub fn read_i32(&mut self) -> Option<i32> {
+ let bytes = self.read_bytes(4)?;
+ Some(i32::from_le_bytes([bytes[0], bytes[1], bytes[2], bytes[3]]))
+ }
+
+ pub fn read_u64(&mut self) -> Option<u64> {
+ let bytes = self.read_bytes(8)?;
+ Some(u64::from_le_bytes([
+ bytes[0], bytes[1], bytes[2], bytes[3],
+ bytes[4], bytes[5], bytes[6], bytes[7]
+ ]))
+ }
+
+ pub fn read_i64(&mut self) -> Option<i64> {
+ let bytes = self.read_bytes(8)?;
+ Some(i64::from_le_bytes([
+ bytes[0], bytes[1], bytes[2], bytes[3],
+ bytes[4], bytes[5], bytes[6], bytes[7]
+ ]))
+ }
+
+ pub fn skip(&mut self, len: usize) {
+ self.pos = (self.pos + len).min(self.data.len());
+ }
+}
+
+// Structure layout definitions for calculating sizes
+pub fn param_spec_layout_size() -> usize {
+ // Packed structure from struct kapi_param_spec
+ sizes::NAME + // name
+ sizes::NAME + // type_name
+ 4 + // type (enum)
+ 4 + // flags
+ 8 + // size (size_t)
+ 8 + // alignment (size_t)
+ 8 + // min_value
+ 8 + // max_value
+ 8 + // valid_mask
+ 8 + // enum_values pointer
+ 4 + // enum_count
+ 4 + // constraint_type (enum)
+ 8 + // validate function pointer
+ sizes::DESC + // description
+ sizes::DESC + // constraints
+ 4 + // size_param_idx
+ 8 + // size_multiplier (size_t)
+ // sysfs-specific fields
+ sizes::NAME + // sysfs_path
+ 2 + // sysfs_permissions (umode_t)
+ sizes::NAME + // default_value
+ 32 + // units
+ 8 + // step
+ 8 + // allowed_strings pointer
+ 4 // allowed_string_count
+}
+
+pub fn return_spec_layout_size() -> usize {
+ // Packed structure from struct kapi_return_spec
+ sizes::NAME + // type_name
+ 4 + // type (enum)
+ 4 + // check_type (enum)
+ 8 + // success_value
+ 8 + // success_min
+ 8 + // success_max
+ 8 + // error_values pointer
+ 4 + // error_count
+ 8 + // is_success function pointer
+ sizes::DESC // description
+}
+
+pub fn error_spec_layout_size() -> usize {
+ // Packed structure
+ 4 + // code
+ sizes::NAME + // name
+ sizes::DESC * 2 // condition, description
+}
+
+pub fn lock_spec_layout_size() -> usize {
+ // Packed structure
+ sizes::NAME + // name
+ 4 + // lock_type
+ 1 + 1 + 1 + 1 + // bools
+ sizes::DESC // description
+}
+
+pub fn constraint_spec_layout_size() -> usize {
+ // Packed structure
+ sizes::NAME + // name
+ sizes::DESC * 2 // description, expression
+}
+
+pub fn capability_spec_layout_size() -> usize {
+ // Packed structure from struct kapi_capability_spec
+ 4 + // capability (int)
+ sizes::NAME + // cap_name
+ 4 + // action (enum)
+ sizes::DESC + // allows
+ sizes::DESC + // without_cap
+ sizes::DESC + // check_condition
+ 1 + // priority (u8)
+ 4 * sizes::MAX_CAPABILITIES + // alternative array
+ 4 // alternative_count
+}
+
+pub fn signal_spec_layout_size() -> usize {
+ // Packed structure from struct kapi_signal_spec
+ 4 + // signal_num
+ 32 + // signal_name[32]
+ 4 + // direction (u32)
+ 4 + // action (enum)
+ sizes::DESC + // target
+ sizes::DESC + // condition
+ sizes::DESC + // description
+ 1 + // restartable (bool)
+ 4 + // sa_flags_required
+ 4 + // sa_flags_forbidden
+ 4 + // error_on_signal
+ 4 + // transform_to
+ 32 + // timing[32]
+ 1 + // priority (u8)
+ 1 + // interruptible (bool)
+ 128 + // queue_behavior[128]
+ 4 + // state_required
+ 4 // state_forbidden
+}
+
+pub fn signal_mask_spec_layout_size() -> usize {
+ // Packed structure from struct kapi_signal_mask_spec
+ sizes::NAME + // mask_name
+ 4 * sizes::MAX_SIGNALS + // signals array
+ 4 + // signal_count
+ sizes::DESC // description
+}
+
+pub fn struct_field_layout_size() -> usize {
+ // Packed structure from struct kapi_struct_field
+ sizes::NAME + // name
+ 4 + // type (enum)
+ sizes::NAME + // type_name
+ 8 + // offset (size_t)
+ 8 + // size (size_t)
+ 4 + // flags
+ 4 + // constraint_type (enum)
+ 8 + // min_value (s64)
+ 8 + // max_value (s64)
+ 8 + // valid_mask (u64)
+ sizes::DESC // description
+}
+
+pub fn struct_spec_layout_size() -> usize {
+ // Packed structure from struct kapi_struct_spec
+ sizes::NAME + // name
+ 8 + // size (size_t)
+ 8 + // alignment (size_t)
+ 4 + // field_count
+ struct_field_layout_size() * sizes::MAX_PARAMS + // fields array
+ sizes::DESC // description
+}
+
+pub fn side_effect_layout_size() -> usize {
+ // Packed structure from struct kapi_side_effect
+ 4 + // type (u32)
+ sizes::NAME + // target
+ sizes::DESC + // condition
+ sizes::DESC + // description
+ 1 // reversible (bool)
+}
+
+pub fn state_transition_layout_size() -> usize {
+ // Packed structure from struct kapi_state_transition
+ sizes::NAME + // from_state
+ sizes::NAME + // to_state
+ sizes::DESC + // condition
+ sizes::NAME + // object
+ sizes::DESC // description
+}
+
+pub fn socket_state_spec_layout_size() -> usize {
+ // struct kapi_socket_state_spec
+ sizes::NAME * sizes::MAX_CONSTRAINTS + // required_states array
+ sizes::NAME * sizes::MAX_CONSTRAINTS + // forbidden_states array
+ sizes::NAME + // resulting_state
+ sizes::DESC + // condition
+ sizes::NAME + // applicable_protocols
+ 4 + // required_count
+ 4 // forbidden_count
+}
+
+pub fn protocol_behavior_spec_layout_size() -> usize {
+ // struct kapi_protocol_behavior
+ sizes::NAME + // applicable_protocols
+ sizes::DESC + // behavior
+ sizes::NAME + // protocol_flags
+ sizes::DESC // flag_description
+}
+
+pub fn buffer_spec_layout_size() -> usize {
+ // struct kapi_buffer_spec
+ sizes::DESC + // buffer_behaviors
+ 8 + // min_buffer_size (size_t)
+ 8 + // max_buffer_size (size_t)
+ 8 // optimal_buffer_size (size_t)
+}
+
+pub fn async_spec_layout_size() -> usize {
+ // struct kapi_async_spec
+ sizes::NAME + // supported_modes
+ 4 // nonblock_errno (int)
+}
+
+pub fn addr_family_spec_layout_size() -> usize {
+ // struct kapi_addr_family_spec
+ 4 + // family (int)
+ sizes::NAME + // family_name
+ 8 + // addr_struct_size (size_t)
+ 8 + // min_addr_len (size_t)
+ 8 + // max_addr_len (size_t)
+ sizes::DESC + // addr_format
+ 1 + // supports_wildcard (bool)
+ 1 + // supports_multicast (bool)
+ 1 + // supports_broadcast (bool)
+ sizes::DESC + // special_addresses
+ 4 + // port_range_min (u32)
+ 4 // port_range_max (u32)
+}
\ No newline at end of file
diff --git a/tools/kapi/src/extractor/vmlinux/mod.rs b/tools/kapi/src/extractor/vmlinux/mod.rs
new file mode 100644
index 0000000000000..b04fc6fe5f630
--- /dev/null
+++ b/tools/kapi/src/extractor/vmlinux/mod.rs
@@ -0,0 +1,989 @@
+use anyhow::{Context, Result};
+use goblin::elf::Elf;
+use std::fs;
+use std::io::Write;
+use std::convert::TryInto;
+use crate::formatter::OutputFormatter;
+use super::{ApiExtractor, ApiSpec, CapabilitySpec, ParamSpec, ReturnSpec, ErrorSpec,
+ SignalSpec, SignalMaskSpec, SideEffectSpec, StateTransitionSpec, ConstraintSpec, LockSpec};
+
+mod binary_utils;
+use binary_utils::{sizes, DataReader,
+ param_spec_layout_size, return_spec_layout_size, error_spec_layout_size,
+ lock_spec_layout_size, constraint_spec_layout_size, capability_spec_layout_size,
+ signal_spec_layout_size, signal_mask_spec_layout_size, struct_spec_layout_size,
+ side_effect_layout_size, state_transition_layout_size, socket_state_spec_layout_size,
+ protocol_behavior_spec_layout_size, buffer_spec_layout_size, async_spec_layout_size,
+ addr_family_spec_layout_size};
+
+pub struct VmlinuxExtractor {
+ kapi_data: Vec<u8>,
+ specs: Vec<KapiSpec>,
+}
+
+#[derive(Debug)]
+struct KapiSpec {
+ name: String,
+ api_type: String,
+ offset: usize,
+}
+
+impl VmlinuxExtractor {
+ pub fn new(vmlinux_path: &str) -> Result<Self> {
+ let vmlinux_data = fs::read(&vmlinux_path)
+ .with_context(|| format!("Failed to read vmlinux file: {vmlinux_path}"))?;
+
+ let elf = Elf::parse(&vmlinux_data)
+ .context("Failed to parse ELF file")?;
+
+ // Find the .kapi_specs section
+ let kapi_section = elf.section_headers
+ .iter()
+ .find(|sh| {
+ if let Some(name) = elf.shdr_strtab.get_at(sh.sh_name) {
+ name == ".kapi_specs"
+ } else {
+ false
+ }
+ })
+ .context("Could not find .kapi_specs section in vmlinux")?;
+
+ // Find __start_kapi_specs and __stop_kapi_specs symbols
+ let mut start_addr = None;
+ let mut stop_addr = None;
+
+ for sym in &elf.syms {
+ if let Some(name) = elf.strtab.get_at(sym.st_name) {
+ match name {
+ "__start_kapi_specs" => start_addr = Some(sym.st_value),
+ "__stop_kapi_specs" => stop_addr = Some(sym.st_value),
+ _ => {}
+ }
+ }
+ }
+
+ let start = start_addr.context("Could not find __start_kapi_specs symbol")?;
+ let stop = stop_addr.context("Could not find __stop_kapi_specs symbol")?;
+
+ if stop <= start {
+ anyhow::bail!("No kernel API specifications found in vmlinux");
+ }
+
+ // Calculate the offset within the file
+ let section_vaddr = kapi_section.sh_addr;
+ let file_offset = kapi_section.sh_offset + (start - section_vaddr);
+ let data_size: usize = (stop - start)
+ .try_into()
+ .context("Data size too large for platform")?;
+
+ let file_offset_usize: usize = file_offset
+ .try_into()
+ .context("File offset too large for platform")?;
+
+ if file_offset_usize + data_size > vmlinux_data.len() {
+ anyhow::bail!("Invalid offset/size for .kapi_specs data");
+ }
+
+ // Extract the raw data
+ let kapi_data = vmlinux_data[file_offset_usize..(file_offset_usize + data_size)].to_vec();
+
+ // Parse the specifications
+ let specs = parse_kapi_specs(&kapi_data)?;
+
+ Ok(VmlinuxExtractor {
+ kapi_data,
+ specs,
+ })
+ }
+
+}
+
+impl ApiExtractor for VmlinuxExtractor {
+ fn extract_all(&self) -> Result<Vec<ApiSpec>> {
+ // For vmlinux extractor, we return basic info only
+ // Detailed parsing happens in display_api_details
+ Ok(self.specs.iter().map(|spec| {
+ ApiSpec {
+ name: spec.name.clone(),
+ api_type: spec.api_type.clone(),
+ description: None,
+ long_description: None,
+ version: None,
+ context_flags: vec![],
+ param_count: None,
+ error_count: None,
+ examples: None,
+ notes: None,
+ since_version: None,
+ subsystem: None,
+ sysfs_path: None,
+ permissions: None,
+ socket_state: None,
+ protocol_behaviors: vec![],
+ addr_families: vec![],
+ buffer_spec: None,
+ async_spec: None,
+ net_data_transfer: None,
+ capabilities: vec![],
+ parameters: vec![],
+ return_spec: None,
+ errors: vec![],
+ signals: vec![],
+ signal_masks: vec![],
+ side_effects: vec![],
+ state_transitions: vec![],
+ constraints: vec![],
+ locks: vec![],
+ }
+ }).collect())
+ }
+
+ fn extract_by_name(&self, name: &str) -> Result<Option<ApiSpec>> {
+ Ok(self.specs.iter()
+ .find(|s| s.name == name)
+ .map(|spec| ApiSpec {
+ name: spec.name.clone(),
+ api_type: spec.api_type.clone(),
+ description: None,
+ long_description: None,
+ version: None,
+ context_flags: vec![],
+ param_count: None,
+ error_count: None,
+ examples: None,
+ notes: None,
+ since_version: None,
+ subsystem: None,
+ sysfs_path: None,
+ permissions: None,
+ socket_state: None,
+ protocol_behaviors: vec![],
+ addr_families: vec![],
+ buffer_spec: None,
+ async_spec: None,
+ net_data_transfer: None,
+ capabilities: vec![],
+ parameters: vec![],
+ return_spec: None,
+ errors: vec![],
+ signals: vec![],
+ signal_masks: vec![],
+ side_effects: vec![],
+ state_transitions: vec![],
+ constraints: vec![],
+ locks: vec![],
+ }))
+ }
+
+ fn display_api_details(
+ &self,
+ api_name: &str,
+ formatter: &mut dyn OutputFormatter,
+ writer: &mut dyn Write,
+ ) -> Result<()> {
+ if let Some(spec) = self.specs.iter().find(|s| s.name == api_name) {
+ // Parse the binary data into an ApiSpec
+ let api_spec = parse_binary_to_api_spec(&self.kapi_data, spec.offset)?;
+ // Use the common display function
+ super::display_api_spec(&api_spec, formatter, writer)?;
+ }
+ Ok(())
+ }
+}
+
+fn calculate_kernel_api_spec_size() -> usize {
+ // Calculate the total size of struct kernel_api_spec based on field layout
+ // Note: The struct is __attribute__((packed)) in the kernel
+ let _base_size = sizes::NAME + // name (128 bytes)
+ 4 + // api_type (enum, 4 bytes)
+ 4 + // version (u32, 4 bytes)
+ sizes::DESC + // description
+ sizes::DESC * 4 + // long_description
+ 4 + // context_flags
+ 4 + // param_count
+ param_spec_layout_size() * sizes::MAX_PARAMS + // params array
+ return_spec_layout_size() + // return_spec
+ 4 + // error_count
+ error_spec_layout_size() * sizes::MAX_ERRORS + // errors array
+ 4 + // lock_count
+ lock_spec_layout_size() * sizes::MAX_CONSTRAINTS + // locks array
+ 4 + // constraint_count
+ constraint_spec_layout_size() * sizes::MAX_CONSTRAINTS + // constraints array
+ sizes::DESC * 2 + // examples
+ sizes::DESC * 2 + // notes
+ 32 + // since_version[32]
+ 1 + // deprecated (bool)
+ sizes::NAME + // replacement
+ 4 + // signal_count
+ signal_spec_layout_size() * sizes::MAX_SIGNALS + // signals array
+ 4 + // signal_mask_count
+ signal_mask_spec_layout_size() * sizes::MAX_SIGNALS + // signal_masks array
+ 4 + // struct_spec_count
+ struct_spec_layout_size() * sizes::MAX_STRUCT_SPECS + // struct_specs array
+ 4 + // side_effect_count
+ side_effect_layout_size() * sizes::MAX_SIDE_EFFECTS + // side_effects array
+ 4 + // state_trans_count
+ state_transition_layout_size() * sizes::MAX_STATE_TRANS + // state_transitions array
+ 4 + // capability_count
+ capability_spec_layout_size() * sizes::MAX_CAPABILITIES + // capabilities array
+ sizes::NAME + // subsystem
+ sizes::NAME; // device_type
+
+ // Add networking-specific fields (CONFIG_NET)
+ // These are part of the kernel struct when CONFIG_NET is enabled
+ let _net_fields_size =
+ // struct kapi_socket_state_spec socket_state
+ socket_state_spec_layout_size() +
+ // struct kapi_protocol_behavior protocol_behaviors[KAPI_MAX_PROTOCOL_BEHAVIORS]
+ protocol_behavior_spec_layout_size() * 8 + // KAPI_MAX_PROTOCOL_BEHAVIORS = 8
+ 4 + // u32 protocol_behavior_count
+ // struct kapi_buffer_spec buffer_spec
+ buffer_spec_layout_size() +
+ // struct kapi_async_spec async_spec
+ async_spec_layout_size() +
+ // struct kapi_addr_family_spec addr_families[KAPI_MAX_ADDR_FAMILIES]
+ addr_family_spec_layout_size() * 8 + // KAPI_MAX_ADDR_FAMILIES = 8
+ 4 + // u32 addr_family_count
+ // Network operation characteristics (6 bools)
+ 6 + // 6 bool fields
+ // Network semantic descriptions (3 strings)
+ sizes::DESC * 3; // connection_establishment, connection_termination, data_transfer_semantics
+
+ // Add IOCTL-specific fields
+ let _ioctl_fields_size =
+ 4 + // unsigned int cmd
+ sizes::NAME + // char cmd_name[KAPI_MAX_NAME_LEN]
+ 8 + // size_t input_size (assuming 64-bit)
+ 8 + // size_t output_size (assuming 64-bit)
+ sizes::NAME; // char file_ops_name[KAPI_MAX_NAME_LEN]
+
+ // Return the observed kernel struct size (355033 bytes + 7 bytes padding)
+ 355_040
+}
+
+fn parse_kapi_specs(data: &[u8]) -> Result<Vec<KapiSpec>> {
+ let mut specs = Vec::new();
+
+ // Calculate the struct size dynamically
+ let struct_size = calculate_kernel_api_spec_size();
+
+ let mut offset = 0;
+ while offset + struct_size <= data.len() {
+ // Try to read the name at this offset
+ if let Some(name) = read_cstring(data, offset, 128) {
+ if is_valid_api_name(&name) {
+ // Read the api_type enum field (4 bytes after the name)
+ let api_type_offset = offset + 128;
+ let api_type = if api_type_offset + 4 <= data.len() {
+ let api_type_value = u32::from_le_bytes([
+ data[api_type_offset],
+ data[api_type_offset + 1],
+ data[api_type_offset + 2],
+ data[api_type_offset + 3],
+ ]);
+
+ match api_type_value {
+ 0 => "function", // KAPI_API_FUNCTION
+ 1 => "ioctl", // KAPI_API_IOCTL
+ 2 => "sysfs", // KAPI_API_SYSFS
+ _ => "unknown",
+ }
+ } else {
+ "unknown"
+ };
+
+ specs.push(KapiSpec {
+ name: name.to_string(),
+ api_type: api_type.to_string(),
+ offset,
+ });
+ }
+ }
+
+ offset += struct_size;
+ }
+
+ // Handle any remaining data that might be a partial spec
+ if offset < data.len() && data.len() - offset >= 128 + 4 {
+ if let Some(name) = read_cstring(data, offset, 128) {
+ if is_valid_api_name(&name) {
+ // Read the api_type enum field
+ let api_type_offset = offset + 128;
+ let api_type = if api_type_offset + 4 <= data.len() {
+ let api_type_value = u32::from_le_bytes([
+ data[api_type_offset],
+ data[api_type_offset + 1],
+ data[api_type_offset + 2],
+ data[api_type_offset + 3],
+ ]);
+
+ match api_type_value {
+ 0 => "function", // KAPI_API_FUNCTION
+ 1 => "ioctl", // KAPI_API_IOCTL
+ 2 => "sysfs", // KAPI_API_SYSFS
+ _ => "unknown",
+ }
+ } else {
+ "unknown"
+ };
+
+ specs.push(KapiSpec {
+ name: name.to_string(),
+ api_type: api_type.to_string(),
+ offset,
+ });
+ }
+ }
+ }
+
+ Ok(specs)
+}
+
+fn read_cstring(data: &[u8], offset: usize, max_len: usize) -> Option<String> {
+ if offset + max_len > data.len() {
+ return None;
+ }
+
+ let bytes = &data[offset..offset + max_len];
+ if let Some(null_pos) = bytes.iter().position(|&b| b == 0) {
+ if null_pos > 0 {
+ if let Ok(s) = std::str::from_utf8(&bytes[..null_pos]) {
+ return Some(s.to_string());
+ }
+ }
+ }
+ None
+}
+
+fn is_valid_api_name(name: &str) -> bool {
+ if name.is_empty() || name.len() > 100 {
+ return false;
+ }
+
+ // Just validate it's a proper identifier since we now use api_type field
+ name.chars().all(|c| c.is_ascii_alphanumeric() || c == '_')
+}
+
+fn parse_binary_to_api_spec(data: &[u8], offset: usize) -> Result<ApiSpec> {
+ let mut reader = DataReader::new(data, offset);
+
+ // Read name
+ let name = reader.read_cstring(sizes::NAME)
+ .ok_or_else(|| anyhow::anyhow!("Failed to read API name"))?;
+
+ // Read api_type enum
+ let api_type = reader.read_u32()
+ .map(|v| match v {
+ 0 => "function", // KAPI_API_FUNCTION
+ 1 => "ioctl", // KAPI_API_IOCTL
+ 2 => "sysfs", // KAPI_API_SYSFS
+ _ => "unknown",
+ })
+ .unwrap_or("unknown")
+ .to_string();
+
+ // Read version
+ let version = reader.read_u32()
+ .map(|v| v.to_string());
+
+ // Read description
+ let description = reader.read_cstring(sizes::DESC)
+ .filter(|s| !s.is_empty());
+
+ // Read long description
+ let long_description = reader.read_cstring(sizes::DESC * 4)
+ .filter(|s| !s.is_empty());
+
+ // Read context flags
+ const KAPI_CTX_PROCESS: u32 = 1 << 0;
+ const KAPI_CTX_SOFTIRQ: u32 = 1 << 1;
+ const KAPI_CTX_HARDIRQ: u32 = 1 << 2;
+ const KAPI_CTX_NMI: u32 = 1 << 3;
+ const KAPI_CTX_USER: u32 = 1 << 4;
+ const KAPI_CTX_KERNEL: u32 = 1 << 5;
+ const KAPI_CTX_SLEEPABLE: u32 = 1 << 6;
+ const KAPI_CTX_ATOMIC: u32 = 1 << 7;
+ const KAPI_CTX_PREEMPTIBLE: u32 = 1 << 8;
+ const KAPI_CTX_MIGRATION_DISABLED: u32 = 1 << 9;
+
+ let context_flags = if let Some(flags) = reader.read_u32() {
+ let mut flag_strings = Vec::new();
+
+ // Build the flag string similar to source format
+ let mut parts = Vec::new();
+ if flags & KAPI_CTX_PROCESS != 0 { parts.push("KAPI_CTX_PROCESS"); }
+ if flags & KAPI_CTX_SOFTIRQ != 0 { parts.push("KAPI_CTX_SOFTIRQ"); }
+ if flags & KAPI_CTX_HARDIRQ != 0 { parts.push("KAPI_CTX_HARDIRQ"); }
+ if flags & KAPI_CTX_NMI != 0 { parts.push("KAPI_CTX_NMI"); }
+ if flags & KAPI_CTX_USER != 0 { parts.push("KAPI_CTX_USER"); }
+ if flags & KAPI_CTX_KERNEL != 0 { parts.push("KAPI_CTX_KERNEL"); }
+ if flags & KAPI_CTX_SLEEPABLE != 0 { parts.push("KAPI_CTX_SLEEPABLE"); }
+ if flags & KAPI_CTX_ATOMIC != 0 { parts.push("KAPI_CTX_ATOMIC"); }
+ if flags & KAPI_CTX_PREEMPTIBLE != 0 { parts.push("KAPI_CTX_PREEMPTIBLE"); }
+ if flags & KAPI_CTX_MIGRATION_DISABLED != 0 { parts.push("KAPI_CTX_MIGRATION_DISABLED"); }
+
+ if !parts.is_empty() {
+ flag_strings.push(parts.join(" | "));
+ }
+ flag_strings
+ } else {
+ vec![]
+ };
+
+ // Read parameter count
+ let param_count = reader.read_u32();
+
+ // Parse parameters
+ let mut parameters = Vec::new();
+ if let Some(count) = param_count {
+ if count > 0 && count as usize <= sizes::MAX_PARAMS {
+ for i in 0..count {
+ if let Some(mut param) = parse_parameter(&mut reader) {
+ param.index = i;
+ parameters.push(param);
+ }
+ }
+ // Skip remaining slots
+ reader.skip(param_spec_layout_size() * (sizes::MAX_PARAMS - count as usize));
+ } else {
+ reader.skip(param_spec_layout_size() * sizes::MAX_PARAMS);
+ }
+ }
+
+ // Parse return spec
+ let return_spec = parse_return_spec(&mut reader);
+
+ // Read error count
+ let error_count = reader.read_u32();
+
+ // Parse errors
+ let mut errors = Vec::new();
+ if let Some(count) = error_count {
+ if count > 0 && count as usize <= sizes::MAX_ERRORS {
+ for _ in 0..count {
+ if let Some(error) = parse_error(&mut reader) {
+ errors.push(error);
+ }
+ }
+ // Skip remaining slots
+ reader.skip(error_spec_layout_size() * (sizes::MAX_ERRORS - count as usize));
+ } else {
+ reader.skip(error_spec_layout_size() * sizes::MAX_ERRORS);
+ }
+ }
+
+ // Parse locks
+ let mut locks = Vec::new();
+ if let Some(count) = reader.read_u32() {
+ if count > 0 && count as usize <= sizes::MAX_CONSTRAINTS {
+ for _ in 0..count {
+ if let Some(lock) = parse_lock(&mut reader) {
+ locks.push(lock);
+ }
+ }
+ // Skip remaining slots
+ reader.skip(lock_spec_layout_size() * (sizes::MAX_CONSTRAINTS - count as usize));
+ } else {
+ reader.skip(lock_spec_layout_size() * sizes::MAX_CONSTRAINTS);
+ }
+ }
+
+ // Parse constraints
+ let mut constraints = Vec::new();
+ if let Some(count) = reader.read_u32() {
+ if count > 0 && count as usize <= sizes::MAX_CONSTRAINTS {
+ for _ in 0..count {
+ if let Some(constraint) = parse_constraint(&mut reader) {
+ constraints.push(constraint);
+ }
+ }
+ // Skip remaining slots
+ reader.skip(constraint_spec_layout_size() * (sizes::MAX_CONSTRAINTS - count as usize));
+ } else {
+ reader.skip(constraint_spec_layout_size() * sizes::MAX_CONSTRAINTS);
+ }
+ }
+
+ // Read examples
+ let examples = reader.read_cstring(sizes::DESC * 2)
+ .filter(|s| !s.is_empty());
+
+ // Read notes
+ let notes = reader.read_cstring(sizes::DESC * 2)
+ .filter(|s| !s.is_empty());
+
+ // Read since_version
+ let since_version = reader.read_cstring(32)
+ .filter(|s| !s.is_empty());
+
+ // Skip deprecated and replacement
+ reader.skip(1); // deprecated (bool)
+ reader.skip(sizes::NAME); // replacement
+
+ // Parse signals
+ let mut signals = Vec::new();
+ if let Some(count) = reader.read_u32() {
+ if count > 0 && count as usize <= sizes::MAX_SIGNALS {
+ for _ in 0..count {
+ if let Some(signal) = parse_signal(&mut reader) {
+ signals.push(signal);
+ }
+ }
+ // Skip remaining slots
+ reader.skip(signal_spec_layout_size() * (sizes::MAX_SIGNALS - count as usize));
+ } else {
+ reader.skip(signal_spec_layout_size() * sizes::MAX_SIGNALS);
+ }
+ }
+
+ // Parse signal masks
+ let signal_mask_count = reader.read_u32();
+ let mut signal_masks = Vec::new();
+ if let Some(count) = signal_mask_count {
+ if count > 0 && count as usize <= sizes::MAX_SIGNALS {
+ for _ in 0..count {
+ if let Some(mask) = parse_signal_mask(&mut reader) {
+ signal_masks.push(mask);
+ }
+ }
+ // Skip remaining slots
+ reader.skip(signal_mask_spec_layout_size() * (sizes::MAX_SIGNALS - count as usize));
+ } else {
+ reader.skip(signal_mask_spec_layout_size() * sizes::MAX_SIGNALS);
+ }
+ }
+
+ // Skip struct specs
+ if let Some(struct_spec_count) = reader.read_u32() {
+ if struct_spec_count > 0 && struct_spec_count as usize <= sizes::MAX_STRUCT_SPECS {
+ reader.skip(struct_spec_layout_size() * struct_spec_count as usize);
+ reader.skip(struct_spec_layout_size() * (sizes::MAX_STRUCT_SPECS - struct_spec_count as usize));
+ } else {
+ reader.skip(struct_spec_layout_size() * sizes::MAX_STRUCT_SPECS);
+ }
+ }
+
+ // Parse side effects
+ let mut side_effects = Vec::new();
+ if let Some(count) = reader.read_u32() {
+ if count > 0 && count as usize <= sizes::MAX_SIDE_EFFECTS {
+ for _ in 0..count {
+ if let Some(effect) = parse_side_effect(&mut reader) {
+ side_effects.push(effect);
+ }
+ }
+ // Skip remaining slots
+ reader.skip(side_effect_layout_size() * (sizes::MAX_SIDE_EFFECTS - count as usize));
+ } else {
+ reader.skip(side_effect_layout_size() * sizes::MAX_SIDE_EFFECTS);
+ }
+ }
+
+ // Parse state transitions
+ let mut state_transitions = Vec::new();
+ if let Some(count) = reader.read_u32() {
+ if count > 0 && count as usize <= sizes::MAX_STATE_TRANS {
+ for _ in 0..count {
+ if let Some(trans) = parse_state_transition(&mut reader) {
+ state_transitions.push(trans);
+ }
+ }
+ // Skip remaining slots
+ reader.skip(state_transition_layout_size() * (sizes::MAX_STATE_TRANS - count as usize));
+ } else {
+ reader.skip(state_transition_layout_size() * sizes::MAX_STATE_TRANS);
+ }
+ }
+
+ // Read capabilities
+ let mut capabilities = Vec::new();
+ if let Some(capability_count) = reader.read_u32() {
+ if capability_count > 0 && capability_count as usize <= sizes::MAX_CAPABILITIES {
+ for _ in 0..capability_count {
+ if let Some(cap) = parse_capability(&mut reader) {
+ capabilities.push(cap);
+ }
+ }
+ // Skip remaining slots
+ reader.skip(capability_spec_layout_size() * (sizes::MAX_CAPABILITIES - capability_count as usize));
+ } else {
+ reader.skip(capability_spec_layout_size() * sizes::MAX_CAPABILITIES);
+ }
+ }
+
+
+ // Sysfs fields not yet available in binary format
+ let subsystem = None;
+ let sysfs_path = None;
+ let permissions = None;
+
+ Ok(ApiSpec {
+ name,
+ api_type,
+ description,
+ long_description,
+ version,
+ context_flags,
+ param_count,
+ error_count,
+ examples,
+ notes,
+ since_version,
+ subsystem,
+ sysfs_path,
+ permissions,
+ socket_state: None,
+ protocol_behaviors: vec![],
+ addr_families: vec![],
+ buffer_spec: None,
+ async_spec: None,
+ net_data_transfer: None,
+ capabilities,
+ parameters,
+ return_spec,
+ errors,
+ signals,
+ signal_masks,
+ side_effects,
+ state_transitions,
+ constraints,
+ locks,
+ })
+}
+
+// Parse a single capability from the binary data
+fn parse_capability(reader: &mut DataReader) -> Option<CapabilitySpec> {
+ let capability = reader.read_i32()?;
+ let cap_name = reader.read_cstring(sizes::NAME)?;
+ let action = reader.read_u32()?;
+ let allows = reader.read_cstring(sizes::DESC).unwrap_or_default();
+ let without_cap = reader.read_cstring(sizes::DESC).unwrap_or_default();
+ let check_condition = reader.read_cstring(sizes::DESC).filter(|s| !s.is_empty());
+ let priority = reader.read_u8();
+
+ // Read alternatives array
+ let mut alternatives = Vec::new();
+ for _ in 0..sizes::MAX_CAPABILITIES {
+ if let Some(alt) = reader.read_i32() {
+ if alt != 0 && alt != -1 {
+ alternatives.push(alt);
+ }
+ }
+ }
+
+ let _alternative_count = reader.read_u32();
+
+ // Convert action enum value to string
+ let action_str = match action {
+ 0 => "Bypasses check",
+ 1 => "Increases limit",
+ 2 => "Overrides restriction",
+ 3 => "Grants permission",
+ 4 => "Modifies behavior",
+ 5 => "Allows resource access",
+ 6 => "Allows operation",
+ _ => "Unknown action",
+ }.to_string();
+
+ Some(CapabilitySpec {
+ capability,
+ name: cap_name,
+ action: action_str,
+ allows,
+ without_cap,
+ check_condition,
+ priority,
+ alternatives,
+ })
+}
+
+// Parse a single parameter from the binary data
+fn parse_parameter(reader: &mut DataReader) -> Option<ParamSpec> {
+ let name = reader.read_cstring(sizes::NAME)?;
+ let type_name = reader.read_cstring(sizes::NAME)?;
+ let param_type = reader.read_u32()?;
+ let flags = reader.read_u32()?;
+ let size = reader.read_u64()?;
+ let alignment = reader.read_u64()?;
+ let min_value = reader.read_i64();
+ let max_value = reader.read_i64();
+ let valid_mask = reader.read_u64();
+ reader.skip(8); // enum_values pointer
+ let _enum_count = reader.read_u32()?;
+ let constraint_type = reader.read_u32()?;
+ reader.skip(8); // validate function pointer
+ let description = reader.read_cstring(sizes::DESC).unwrap_or_default();
+ let constraint = reader.read_cstring(sizes::DESC).filter(|s| !s.is_empty());
+ let _size_param_idx = reader.read_i32();
+ let _size_multiplier = reader.read_u64();
+ // Skip sysfs-specific fields
+ reader.skip(sizes::NAME); // sysfs_path
+ reader.skip(2); // sysfs_permissions (umode_t)
+ reader.skip(sizes::NAME); // default_value
+ reader.skip(32); // units
+ reader.skip(8); // step
+ reader.skip(8); // allowed_strings pointer
+ reader.skip(4); // allowed_string_count
+
+ // Calculate parameter index from position
+ let index = 0; // Will be set by caller
+
+ Some(ParamSpec {
+ index,
+ name,
+ type_name,
+ description,
+ flags,
+ param_type,
+ constraint_type,
+ constraint,
+ min_value,
+ max_value,
+ valid_mask,
+ enum_values: vec![], // Can't read from binary pointers
+ size: Some(size.try_into().unwrap_or(u32::MAX)),
+ alignment: Some(alignment.try_into().unwrap_or(u32::MAX)),
+ })
+}
+
+// Parse return specification from the binary data
+fn parse_return_spec(reader: &mut DataReader) -> Option<ReturnSpec> {
+ let type_name = reader.read_cstring(sizes::NAME)?;
+ let return_type = reader.read_u32()?;
+ let check_type = reader.read_u32()?;
+ let success_value = reader.read_i64();
+ let success_min = reader.read_i64();
+ let success_max = reader.read_i64();
+ reader.skip(8); // error_values pointer
+ let _error_count = reader.read_u32()?;
+ reader.skip(8); // is_success function pointer
+ let description = reader.read_cstring(sizes::DESC).unwrap_or_default();
+
+ Some(ReturnSpec {
+ type_name,
+ description,
+ return_type,
+ check_type,
+ success_value,
+ success_min,
+ success_max,
+ error_values: vec![], // Can't read from binary pointers
+ })
+}
+
+// Parse a single error specification from the binary data
+fn parse_error(reader: &mut DataReader) -> Option<ErrorSpec> {
+ let error_code = reader.read_i32()?;
+ let name = reader.read_cstring(sizes::NAME)?;
+ let condition = reader.read_cstring(sizes::DESC).unwrap_or_default();
+ let description = reader.read_cstring(sizes::DESC).unwrap_or_default();
+
+ Some(ErrorSpec {
+ error_code,
+ name,
+ condition,
+ description,
+ })
+}
+
+// Parse a single signal specification from the binary data
+fn parse_signal(reader: &mut DataReader) -> Option<SignalSpec> {
+ let signal_num = reader.read_i32()?;
+ let signal_name = reader.read_cstring(32)?; // Fixed size in struct
+ let direction = reader.read_u32()?;
+ let action = reader.read_u32()?;
+ let target = reader.read_cstring(sizes::DESC).filter(|s| !s.is_empty());
+ let condition = reader.read_cstring(sizes::DESC).filter(|s| !s.is_empty());
+ let description = reader.read_cstring(sizes::DESC).filter(|s| !s.is_empty());
+ let restartable = reader.read_u8()? != 0;
+ let sa_flags_required = reader.read_u32()?;
+ let sa_flags_forbidden = reader.read_u32()?;
+ let error_on_signal = reader.read_i32();
+ let _transform_to = reader.read_i32();
+ let timing_str = reader.read_cstring(32)?;
+ let priority = reader.read_u8()? as u32;
+ let interruptible = reader.read_u8()? != 0;
+ let queue = reader.read_cstring(128).filter(|s| !s.is_empty());
+ let state_required = reader.read_u32()?;
+ let state_forbidden = reader.read_u32()?;
+
+ // Convert timing string to enum value
+ let timing = match timing_str.as_str() {
+ "BEFORE" => 0,
+ "AFTER" => 2,
+ "EXIT" => 3,
+ _ => 1, // Default to DURING (includes "DURING")
+ };
+
+ Some(SignalSpec {
+ signal_num,
+ signal_name,
+ direction,
+ action,
+ target,
+ condition,
+ description,
+ timing,
+ priority,
+ restartable,
+ interruptible,
+ queue,
+ sa_flags: 0, // Not in struct
+ sa_flags_required,
+ sa_flags_forbidden,
+ state_required,
+ state_forbidden,
+ error_on_signal,
+ })
+}
+
+// Parse a single signal mask specification from the binary data
+fn parse_signal_mask(reader: &mut DataReader) -> Option<SignalMaskSpec> {
+ let name = reader.read_cstring(sizes::NAME)?;
+ // Skip signals array
+ reader.skip(4 * sizes::MAX_SIGNALS); // int array
+ let _signal_count = reader.read_u32()?;
+ let description = reader.read_cstring(sizes::DESC).unwrap_or_default();
+
+ Some(SignalMaskSpec {
+ name,
+ description,
+ })
+}
+
+// Parse a single side effect specification from the binary data
+fn parse_side_effect(reader: &mut DataReader) -> Option<SideEffectSpec> {
+ let effect_type = reader.read_u32()?;
+ let target = reader.read_cstring(sizes::NAME)?;
+ let condition = reader.read_cstring(sizes::DESC).filter(|s| !s.is_empty());
+ let description = reader.read_cstring(sizes::DESC).unwrap_or_default();
+ let reversible = reader.read_u8()? != 0;
+
+ Some(SideEffectSpec {
+ effect_type,
+ target,
+ condition,
+ description,
+ reversible,
+ })
+}
+
+// Parse a single state transition specification from the binary data
+fn parse_state_transition(reader: &mut DataReader) -> Option<StateTransitionSpec> {
+ let from_state = reader.read_cstring(sizes::NAME)?;
+ let to_state = reader.read_cstring(sizes::NAME)?;
+ let condition = reader.read_cstring(sizes::DESC).filter(|s| !s.is_empty());
+ let object = reader.read_cstring(sizes::NAME)?;
+ let description = reader.read_cstring(sizes::DESC).unwrap_or_default();
+
+ Some(StateTransitionSpec {
+ object,
+ from_state,
+ to_state,
+ condition,
+ description,
+ })
+}
+
+// Parse a single constraint specification from the binary data
+fn parse_constraint(reader: &mut DataReader) -> Option<ConstraintSpec> {
+ let name = reader.read_cstring(sizes::NAME)?;
+ let description = reader.read_cstring(sizes::DESC).unwrap_or_default();
+ let expression = reader.read_cstring(sizes::DESC).filter(|s| !s.is_empty());
+
+ Some(ConstraintSpec {
+ name,
+ description,
+ expression,
+ })
+}
+
+// Parse a single lock specification from the binary data
+fn parse_lock(reader: &mut DataReader) -> Option<LockSpec> {
+ let lock_name = reader.read_cstring(sizes::NAME)?;
+ let lock_type = reader.read_u32()?;
+ let acquired = reader.read_u8()? != 0;
+ let released = reader.read_u8()? != 0;
+ let held_on_entry = reader.read_u8()? != 0;
+ let held_on_exit = reader.read_u8()? != 0;
+ let description = reader.read_cstring(sizes::DESC).unwrap_or_default();
+
+ Some(LockSpec {
+ lock_name,
+ lock_type,
+ acquired,
+ released,
+ held_on_entry,
+ held_on_exit,
+ description,
+ })
+}
+
+// Old display_api_details_from_binary function removed - now using parse_binary_to_api_spec + display_api_spec
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+
+ #[test]
+ fn test_parse_capability() {
+ // Create mock binary data for a capability
+ let mut data = Vec::new();
+
+ // capability (i32) = 14 (CAP_IPC_LOCK)
+ data.extend_from_slice(&14i32.to_le_bytes());
+
+ // cap_name (128 bytes) = "CAP_IPC_LOCK"
+ let mut name_bytes = b"CAP_IPC_LOCK".to_vec();
+ name_bytes.resize(128, 0);
+ data.extend_from_slice(&name_bytes);
+
+ // action (u32) = 0 (KAPI_CAP_BYPASS_CHECK)
+ data.extend_from_slice(&0u32.to_le_bytes());
+
+ // allows (512 bytes)
+ let mut allows_bytes = b"Bypass RLIMIT_MEMLOCK check entirely".to_vec();
+ allows_bytes.resize(512, 0);
+ data.extend_from_slice(&allows_bytes);
+
+ // without_cap (512 bytes)
+ let mut without_bytes = b"Must stay within RLIMIT_MEMLOCK".to_vec();
+ without_bytes.resize(512, 0);
+ data.extend_from_slice(&without_bytes);
+
+ // check_condition (512 bytes)
+ let mut condition_bytes = b"When memory would exceed limit".to_vec();
+ condition_bytes.resize(512, 0);
+ data.extend_from_slice(&condition_bytes);
+
+ // priority (u8) = 0
+ data.push(0);
+
+ // alternatives (4 * 8 = 32 bytes) - all zeros
+ data.extend_from_slice(&[0u8; 32]);
+
+ // alternative_count (u32) = 0
+ data.extend_from_slice(&0u32.to_le_bytes());
+
+ // Parse the capability
+ let mut reader = DataReader::new(&data, 0);
+ let cap = parse_capability(&mut reader).unwrap();
+
+ assert_eq!(cap.capability, 14);
+ assert_eq!(cap.name, "CAP_IPC_LOCK");
+ assert_eq!(cap.action, "Bypasses check");
+ assert_eq!(cap.allows, "Bypass RLIMIT_MEMLOCK check entirely");
+ assert_eq!(cap.without_cap, "Must stay within RLIMIT_MEMLOCK");
+ assert_eq!(cap.check_condition, Some("When memory would exceed limit".to_string()));
+ assert_eq!(cap.priority, Some(0));
+ assert!(cap.alternatives.is_empty());
+ }
+
+ #[test]
+ fn test_calculate_struct_size() {
+ let size = calculate_kernel_api_spec_size();
+ // The actual kernel struct size is 308064, our calculation gives 308305
+ // The difference is acceptable for alignment/padding
+ assert!(size > 308000 && size < 309000, "Struct size {} is out of expected range", size);
+ }
+}
\ No newline at end of file
diff --git a/tools/kapi/src/formatter/json.rs b/tools/kapi/src/formatter/json.rs
new file mode 100644
index 0000000000000..836741fdcb91b
--- /dev/null
+++ b/tools/kapi/src/formatter/json.rs
@@ -0,0 +1,420 @@
+use super::OutputFormatter;
+use std::io::Write;
+use serde::Serialize;
+use crate::extractor::{SocketStateSpec, ProtocolBehaviorSpec, AddrFamilySpec, BufferSpec, AsyncSpec, CapabilitySpec,
+ ParamSpec, ReturnSpec, ErrorSpec, SignalSpec, SignalMaskSpec, SideEffectSpec, StateTransitionSpec, ConstraintSpec, LockSpec};
+
+pub struct JsonFormatter {
+ data: JsonData,
+}
+
+#[derive(Serialize)]
+struct JsonData {
+ #[serde(skip_serializing_if = "Option::is_none")]
+ apis: Option<Vec<JsonApi>>,
+ #[serde(skip_serializing_if = "Option::is_none")]
+ api_details: Option<JsonApiDetails>,
+}
+
+#[derive(Serialize)]
+struct JsonApi {
+ name: String,
+ api_type: String,
+}
+
+#[derive(Serialize)]
+struct JsonApiDetails {
+ name: String,
+ #[serde(skip_serializing_if = "Option::is_none")]
+ description: Option<String>,
+ #[serde(skip_serializing_if = "Option::is_none")]
+ long_description: Option<String>,
+ #[serde(skip_serializing_if = "Vec::is_empty")]
+ context_flags: Vec<String>,
+ #[serde(skip_serializing_if = "Option::is_none")]
+ examples: Option<String>,
+ #[serde(skip_serializing_if = "Option::is_none")]
+ notes: Option<String>,
+ #[serde(skip_serializing_if = "Option::is_none")]
+ since_version: Option<String>,
+ // Sysfs-specific fields
+ #[serde(skip_serializing_if = "Option::is_none")]
+ subsystem: Option<String>,
+ #[serde(skip_serializing_if = "Option::is_none")]
+ sysfs_path: Option<String>,
+ #[serde(skip_serializing_if = "Option::is_none")]
+ permissions: Option<String>,
+ // Networking-specific fields
+ #[serde(skip_serializing_if = "Option::is_none")]
+ socket_state: Option<SocketStateSpec>,
+ #[serde(skip_serializing_if = "Vec::is_empty")]
+ protocol_behaviors: Vec<ProtocolBehaviorSpec>,
+ #[serde(skip_serializing_if = "Vec::is_empty")]
+ addr_families: Vec<AddrFamilySpec>,
+ #[serde(skip_serializing_if = "Option::is_none")]
+ buffer_spec: Option<BufferSpec>,
+ #[serde(skip_serializing_if = "Option::is_none")]
+ async_spec: Option<AsyncSpec>,
+ #[serde(skip_serializing_if = "Option::is_none")]
+ net_data_transfer: Option<String>,
+ #[serde(skip_serializing_if = "Vec::is_empty")]
+ capabilities: Vec<CapabilitySpec>,
+ #[serde(skip_serializing_if = "Vec::is_empty")]
+ state_transitions: Vec<StateTransitionSpec>,
+ #[serde(skip_serializing_if = "Vec::is_empty")]
+ side_effects: Vec<SideEffectSpec>,
+ #[serde(skip_serializing_if = "Vec::is_empty")]
+ parameters: Vec<ParamSpec>,
+ #[serde(skip_serializing_if = "Option::is_none")]
+ return_spec: Option<ReturnSpec>,
+ #[serde(skip_serializing_if = "Vec::is_empty")]
+ errors: Vec<ErrorSpec>,
+ #[serde(skip_serializing_if = "Vec::is_empty")]
+ locks: Vec<LockSpec>,
+}
+
+
+impl JsonFormatter {
+ pub fn new() -> Self {
+ JsonFormatter {
+ data: JsonData {
+ apis: None,
+ api_details: None,
+ }
+ }
+ }
+}
+
+impl OutputFormatter for JsonFormatter {
+ fn begin_document(&mut self, _w: &mut dyn Write) -> std::io::Result<()> {
+ Ok(())
+ }
+
+ fn end_document(&mut self, w: &mut dyn Write) -> std::io::Result<()> {
+ let json = serde_json::to_string_pretty(&self.data)?;
+ writeln!(w, "{json}")?;
+ Ok(())
+ }
+
+ fn begin_api_list(&mut self, _w: &mut dyn Write, _title: &str) -> std::io::Result<()> {
+ self.data.apis = Some(Vec::new());
+ Ok(())
+ }
+
+ fn api_item(&mut self, _w: &mut dyn Write, name: &str, api_type: &str) -> std::io::Result<()> {
+ if let Some(apis) = &mut self.data.apis {
+ apis.push(JsonApi {
+ name: name.to_string(),
+ api_type: api_type.to_string(),
+ });
+ }
+ Ok(())
+ }
+
+ fn end_api_list(&mut self, _w: &mut dyn Write) -> std::io::Result<()> {
+ Ok(())
+ }
+
+ fn total_specs(&mut self, _w: &mut dyn Write, _count: usize) -> std::io::Result<()> {
+ Ok(())
+ }
+
+ fn begin_api_details(&mut self, _w: &mut dyn Write, name: &str) -> std::io::Result<()> {
+ self.data.api_details = Some(JsonApiDetails {
+ name: name.to_string(),
+ description: None,
+ long_description: None,
+ context_flags: Vec::new(),
+ examples: None,
+ notes: None,
+ since_version: None,
+ subsystem: None,
+ sysfs_path: None,
+ permissions: None,
+ socket_state: None,
+ protocol_behaviors: Vec::new(),
+ addr_families: Vec::new(),
+ buffer_spec: None,
+ async_spec: None,
+ net_data_transfer: None,
+ capabilities: Vec::new(),
+ state_transitions: Vec::new(),
+ side_effects: Vec::new(),
+ parameters: Vec::new(),
+ return_spec: None,
+ errors: Vec::new(),
+ locks: Vec::new(),
+ });
+ Ok(())
+ }
+
+ fn end_api_details(&mut self, _w: &mut dyn Write) -> std::io::Result<()> {
+ Ok(())
+ }
+
+
+ fn description(&mut self, _w: &mut dyn Write, desc: &str) -> std::io::Result<()> {
+ if let Some(details) = &mut self.data.api_details {
+ details.description = Some(desc.to_string());
+ }
+ Ok(())
+ }
+
+ fn long_description(&mut self, _w: &mut dyn Write, desc: &str) -> std::io::Result<()> {
+ if let Some(details) = &mut self.data.api_details {
+ details.long_description = Some(desc.to_string());
+ }
+ Ok(())
+ }
+
+ fn begin_context_flags(&mut self, _w: &mut dyn Write) -> std::io::Result<()> {
+ Ok(())
+ }
+
+ fn context_flag(&mut self, _w: &mut dyn Write, flag: &str) -> std::io::Result<()> {
+ if let Some(details) = &mut self.data.api_details {
+ details.context_flags.push(flag.to_string());
+ }
+ Ok(())
+ }
+
+ fn end_context_flags(&mut self, _w: &mut dyn Write) -> std::io::Result<()> {
+ Ok(())
+ }
+
+ fn begin_parameters(&mut self, _w: &mut dyn Write, _count: u32) -> std::io::Result<()> {
+ Ok(())
+ }
+
+
+ fn end_parameters(&mut self, _w: &mut dyn Write) -> std::io::Result<()> {
+ Ok(())
+ }
+
+ fn begin_errors(&mut self, _w: &mut dyn Write, _count: u32) -> std::io::Result<()> {
+ Ok(())
+ }
+
+ fn end_errors(&mut self, _w: &mut dyn Write) -> std::io::Result<()> {
+ Ok(())
+ }
+
+ fn examples(&mut self, _w: &mut dyn Write, examples: &str) -> std::io::Result<()> {
+ if let Some(details) = &mut self.data.api_details {
+ details.examples = Some(examples.to_string());
+ }
+ Ok(())
+ }
+
+ fn notes(&mut self, _w: &mut dyn Write, notes: &str) -> std::io::Result<()> {
+ if let Some(details) = &mut self.data.api_details {
+ details.notes = Some(notes.to_string());
+ }
+ Ok(())
+ }
+
+ fn since_version(&mut self, _w: &mut dyn Write, version: &str) -> std::io::Result<()> {
+ if let Some(details) = &mut self.data.api_details {
+ details.since_version = Some(version.to_string());
+ }
+ Ok(())
+ }
+
+ fn sysfs_subsystem(&mut self, _w: &mut dyn Write, subsystem: &str) -> std::io::Result<()> {
+ if let Some(details) = &mut self.data.api_details {
+ details.subsystem = Some(subsystem.to_string());
+ }
+ Ok(())
+ }
+
+ fn sysfs_path(&mut self, _w: &mut dyn Write, path: &str) -> std::io::Result<()> {
+ if let Some(details) = &mut self.data.api_details {
+ details.sysfs_path = Some(path.to_string());
+ }
+ Ok(())
+ }
+
+ fn sysfs_permissions(&mut self, _w: &mut dyn Write, perms: &str) -> std::io::Result<()> {
+ if let Some(details) = &mut self.data.api_details {
+ details.permissions = Some(perms.to_string());
+ }
+ Ok(())
+ }
+
+ // Networking-specific methods
+ fn socket_state(&mut self, _w: &mut dyn Write, state: &SocketStateSpec) -> std::io::Result<()> {
+ if let Some(details) = &mut self.data.api_details {
+ details.socket_state = Some(state.clone());
+ }
+ Ok(())
+ }
+
+ fn begin_protocol_behaviors(&mut self, _w: &mut dyn Write) -> std::io::Result<()> {
+ Ok(())
+ }
+
+ fn protocol_behavior(&mut self, _w: &mut dyn Write, behavior: &ProtocolBehaviorSpec) -> std::io::Result<()> {
+ if let Some(details) = &mut self.data.api_details {
+ details.protocol_behaviors.push(behavior.clone());
+ }
+ Ok(())
+ }
+
+ fn end_protocol_behaviors(&mut self, _w: &mut dyn Write) -> std::io::Result<()> {
+ Ok(())
+ }
+
+ fn begin_addr_families(&mut self, _w: &mut dyn Write) -> std::io::Result<()> {
+ Ok(())
+ }
+
+ fn addr_family(&mut self, _w: &mut dyn Write, family: &AddrFamilySpec) -> std::io::Result<()> {
+ if let Some(details) = &mut self.data.api_details {
+ details.addr_families.push(family.clone());
+ }
+ Ok(())
+ }
+
+ fn end_addr_families(&mut self, _w: &mut dyn Write) -> std::io::Result<()> {
+ Ok(())
+ }
+
+ fn buffer_spec(&mut self, _w: &mut dyn Write, spec: &BufferSpec) -> std::io::Result<()> {
+ if let Some(details) = &mut self.data.api_details {
+ details.buffer_spec = Some(spec.clone());
+ }
+ Ok(())
+ }
+
+ fn async_spec(&mut self, _w: &mut dyn Write, spec: &AsyncSpec) -> std::io::Result<()> {
+ if let Some(details) = &mut self.data.api_details {
+ details.async_spec = Some(spec.clone());
+ }
+ Ok(())
+ }
+
+ fn net_data_transfer(&mut self, _w: &mut dyn Write, desc: &str) -> std::io::Result<()> {
+ if let Some(details) = &mut self.data.api_details {
+ details.net_data_transfer = Some(desc.to_string());
+ }
+ Ok(())
+ }
+
+ fn begin_capabilities(&mut self, _w: &mut dyn Write) -> std::io::Result<()> {
+ Ok(())
+ }
+
+ fn capability(&mut self, _w: &mut dyn Write, cap: &CapabilitySpec) -> std::io::Result<()> {
+ if let Some(details) = &mut self.data.api_details {
+ details.capabilities.push(cap.clone());
+ }
+ Ok(())
+ }
+
+ fn end_capabilities(&mut self, _w: &mut dyn Write) -> std::io::Result<()> {
+ Ok(())
+ }
+
+ // Stub implementations for new methods
+ fn parameter(&mut self, _w: &mut dyn Write, param: &ParamSpec) -> std::io::Result<()> {
+ if let Some(details) = &mut self.data.api_details {
+ details.parameters.push(param.clone());
+ }
+ Ok(())
+ }
+
+ fn return_spec(&mut self, _w: &mut dyn Write, ret: &ReturnSpec) -> std::io::Result<()> {
+ if let Some(details) = &mut self.data.api_details {
+ details.return_spec = Some(ret.clone());
+ }
+ Ok(())
+ }
+
+ fn error(&mut self, _w: &mut dyn Write, error: &ErrorSpec) -> std::io::Result<()> {
+ if let Some(details) = &mut self.data.api_details {
+ details.errors.push(error.clone());
+ }
+ Ok(())
+ }
+
+ fn begin_signals(&mut self, _w: &mut dyn Write, _count: u32) -> std::io::Result<()> {
+ Ok(())
+ }
+
+ fn signal(&mut self, _w: &mut dyn Write, _signal: &SignalSpec) -> std::io::Result<()> {
+ Ok(())
+ }
+
+ fn end_signals(&mut self, _w: &mut dyn Write) -> std::io::Result<()> {
+ Ok(())
+ }
+
+ fn begin_signal_masks(&mut self, _w: &mut dyn Write, _count: u32) -> std::io::Result<()> {
+ Ok(())
+ }
+
+ fn signal_mask(&mut self, _w: &mut dyn Write, _mask: &SignalMaskSpec) -> std::io::Result<()> {
+ Ok(())
+ }
+
+ fn end_signal_masks(&mut self, _w: &mut dyn Write) -> std::io::Result<()> {
+ Ok(())
+ }
+
+ fn begin_side_effects(&mut self, _w: &mut dyn Write, _count: u32) -> std::io::Result<()> {
+ Ok(())
+ }
+
+ fn side_effect(&mut self, _w: &mut dyn Write, effect: &SideEffectSpec) -> std::io::Result<()> {
+ if let Some(details) = &mut self.data.api_details {
+ details.side_effects.push(effect.clone());
+ }
+ Ok(())
+ }
+
+ fn end_side_effects(&mut self, _w: &mut dyn Write) -> std::io::Result<()> {
+ Ok(())
+ }
+
+ fn begin_state_transitions(&mut self, _w: &mut dyn Write, _count: u32) -> std::io::Result<()> {
+ Ok(())
+ }
+
+ fn state_transition(&mut self, _w: &mut dyn Write, trans: &StateTransitionSpec) -> std::io::Result<()> {
+ if let Some(details) = &mut self.data.api_details {
+ details.state_transitions.push(trans.clone());
+ }
+ Ok(())
+ }
+
+ fn end_state_transitions(&mut self, _w: &mut dyn Write) -> std::io::Result<()> {
+ Ok(())
+ }
+
+ fn begin_constraints(&mut self, _w: &mut dyn Write, _count: u32) -> std::io::Result<()> {
+ Ok(())
+ }
+
+ fn constraint(&mut self, _w: &mut dyn Write, _constraint: &ConstraintSpec) -> std::io::Result<()> {
+ Ok(())
+ }
+
+ fn end_constraints(&mut self, _w: &mut dyn Write) -> std::io::Result<()> {
+ Ok(())
+ }
+
+ fn begin_locks(&mut self, _w: &mut dyn Write, _count: u32) -> std::io::Result<()> {
+ Ok(())
+ }
+
+ fn lock(&mut self, _w: &mut dyn Write, lock: &LockSpec) -> std::io::Result<()> {
+ if let Some(details) = &mut self.data.api_details {
+ details.locks.push(lock.clone());
+ }
+ Ok(())
+ }
+
+ fn end_locks(&mut self, _w: &mut dyn Write) -> std::io::Result<()> {
+ Ok(())
+ }
+}
\ No newline at end of file
diff --git a/tools/kapi/src/formatter/mod.rs b/tools/kapi/src/formatter/mod.rs
new file mode 100644
index 0000000000000..ec61827ba47b5
--- /dev/null
+++ b/tools/kapi/src/formatter/mod.rs
@@ -0,0 +1,130 @@
+use std::io::Write;
+use crate::extractor::{SocketStateSpec, ProtocolBehaviorSpec, AddrFamilySpec, BufferSpec, AsyncSpec, CapabilitySpec,
+ ParamSpec, ReturnSpec, ErrorSpec, SignalSpec, SignalMaskSpec, SideEffectSpec, StateTransitionSpec, ConstraintSpec, LockSpec};
+
+mod plain;
+mod json;
+mod rst;
+mod shall;
+
+pub use plain::PlainFormatter;
+pub use json::JsonFormatter;
+pub use rst::RstFormatter;
+pub use shall::ShallFormatter;
+
+
+#[derive(Debug, Clone, Copy, PartialEq)]
+pub enum OutputFormat {
+ Plain,
+ Json,
+ Rst,
+ Shall,
+}
+
+impl std::str::FromStr for OutputFormat {
+ type Err = String;
+
+ fn from_str(s: &str) -> Result<Self, Self::Err> {
+ match s.to_lowercase().as_str() {
+ "plain" => Ok(OutputFormat::Plain),
+ "json" => Ok(OutputFormat::Json),
+ "rst" => Ok(OutputFormat::Rst),
+ "shall" => Ok(OutputFormat::Shall),
+ _ => Err(format!("Unknown output format: {}", s)),
+ }
+ }
+}
+
+pub trait OutputFormatter {
+ fn begin_document(&mut self, w: &mut dyn Write) -> std::io::Result<()>;
+ fn end_document(&mut self, w: &mut dyn Write) -> std::io::Result<()>;
+
+ fn begin_api_list(&mut self, w: &mut dyn Write, title: &str) -> std::io::Result<()>;
+ fn api_item(&mut self, w: &mut dyn Write, name: &str, api_type: &str) -> std::io::Result<()>;
+ fn end_api_list(&mut self, w: &mut dyn Write) -> std::io::Result<()>;
+
+ fn total_specs(&mut self, w: &mut dyn Write, count: usize) -> std::io::Result<()>;
+
+ fn begin_api_details(&mut self, w: &mut dyn Write, name: &str) -> std::io::Result<()>;
+ fn end_api_details(&mut self, w: &mut dyn Write) -> std::io::Result<()>;
+ fn description(&mut self, w: &mut dyn Write, desc: &str) -> std::io::Result<()>;
+ fn long_description(&mut self, w: &mut dyn Write, desc: &str) -> std::io::Result<()>;
+
+ fn begin_context_flags(&mut self, w: &mut dyn Write) -> std::io::Result<()>;
+ fn context_flag(&mut self, w: &mut dyn Write, flag: &str) -> std::io::Result<()>;
+ fn end_context_flags(&mut self, w: &mut dyn Write) -> std::io::Result<()>;
+
+ fn begin_parameters(&mut self, w: &mut dyn Write, count: u32) -> std::io::Result<()>;
+ fn parameter(&mut self, w: &mut dyn Write, param: &ParamSpec) -> std::io::Result<()>;
+ fn end_parameters(&mut self, w: &mut dyn Write) -> std::io::Result<()>;
+
+ fn return_spec(&mut self, w: &mut dyn Write, ret: &ReturnSpec) -> std::io::Result<()>;
+
+ fn begin_errors(&mut self, w: &mut dyn Write, count: u32) -> std::io::Result<()>;
+ fn error(&mut self, w: &mut dyn Write, error: &ErrorSpec) -> std::io::Result<()>;
+ fn end_errors(&mut self, w: &mut dyn Write) -> std::io::Result<()>;
+
+ fn examples(&mut self, w: &mut dyn Write, examples: &str) -> std::io::Result<()>;
+ fn notes(&mut self, w: &mut dyn Write, notes: &str) -> std::io::Result<()>;
+ fn since_version(&mut self, w: &mut dyn Write, version: &str) -> std::io::Result<()>;
+
+ // Sysfs-specific methods
+ fn sysfs_subsystem(&mut self, w: &mut dyn Write, subsystem: &str) -> std::io::Result<()>;
+ fn sysfs_path(&mut self, w: &mut dyn Write, path: &str) -> std::io::Result<()>;
+ fn sysfs_permissions(&mut self, w: &mut dyn Write, perms: &str) -> std::io::Result<()>;
+
+ // Networking-specific methods
+ fn socket_state(&mut self, w: &mut dyn Write, state: &SocketStateSpec) -> std::io::Result<()>;
+
+ fn begin_protocol_behaviors(&mut self, w: &mut dyn Write) -> std::io::Result<()>;
+ fn protocol_behavior(&mut self, w: &mut dyn Write, behavior: &ProtocolBehaviorSpec) -> std::io::Result<()>;
+ fn end_protocol_behaviors(&mut self, w: &mut dyn Write) -> std::io::Result<()>;
+
+ fn begin_addr_families(&mut self, w: &mut dyn Write) -> std::io::Result<()>;
+ fn addr_family(&mut self, w: &mut dyn Write, family: &AddrFamilySpec) -> std::io::Result<()>;
+ fn end_addr_families(&mut self, w: &mut dyn Write) -> std::io::Result<()>;
+
+ fn buffer_spec(&mut self, w: &mut dyn Write, spec: &BufferSpec) -> std::io::Result<()>;
+ fn async_spec(&mut self, w: &mut dyn Write, spec: &AsyncSpec) -> std::io::Result<()>;
+ fn net_data_transfer(&mut self, w: &mut dyn Write, desc: &str) -> std::io::Result<()>;
+
+ fn begin_capabilities(&mut self, w: &mut dyn Write) -> std::io::Result<()>;
+ fn capability(&mut self, w: &mut dyn Write, cap: &CapabilitySpec) -> std::io::Result<()>;
+ fn end_capabilities(&mut self, w: &mut dyn Write) -> std::io::Result<()>;
+
+ // Signal-related methods
+ fn begin_signals(&mut self, w: &mut dyn Write, count: u32) -> std::io::Result<()>;
+ fn signal(&mut self, w: &mut dyn Write, signal: &SignalSpec) -> std::io::Result<()>;
+ fn end_signals(&mut self, w: &mut dyn Write) -> std::io::Result<()>;
+
+ fn begin_signal_masks(&mut self, w: &mut dyn Write, count: u32) -> std::io::Result<()>;
+ fn signal_mask(&mut self, w: &mut dyn Write, mask: &SignalMaskSpec) -> std::io::Result<()>;
+ fn end_signal_masks(&mut self, w: &mut dyn Write) -> std::io::Result<()>;
+
+ // Side effects and state transitions
+ fn begin_side_effects(&mut self, w: &mut dyn Write, count: u32) -> std::io::Result<()>;
+ fn side_effect(&mut self, w: &mut dyn Write, effect: &SideEffectSpec) -> std::io::Result<()>;
+ fn end_side_effects(&mut self, w: &mut dyn Write) -> std::io::Result<()>;
+
+ fn begin_state_transitions(&mut self, w: &mut dyn Write, count: u32) -> std::io::Result<()>;
+ fn state_transition(&mut self, w: &mut dyn Write, trans: &StateTransitionSpec) -> std::io::Result<()>;
+ fn end_state_transitions(&mut self, w: &mut dyn Write) -> std::io::Result<()>;
+
+ // Constraints and locks
+ fn begin_constraints(&mut self, w: &mut dyn Write, count: u32) -> std::io::Result<()>;
+ fn constraint(&mut self, w: &mut dyn Write, constraint: &ConstraintSpec) -> std::io::Result<()>;
+ fn end_constraints(&mut self, w: &mut dyn Write) -> std::io::Result<()>;
+
+ fn begin_locks(&mut self, w: &mut dyn Write, count: u32) -> std::io::Result<()>;
+ fn lock(&mut self, w: &mut dyn Write, lock: &LockSpec) -> std::io::Result<()>;
+ fn end_locks(&mut self, w: &mut dyn Write) -> std::io::Result<()>;
+}
+
+pub fn create_formatter(format: OutputFormat) -> Box<dyn OutputFormatter> {
+ match format {
+ OutputFormat::Plain => Box::new(PlainFormatter::new()),
+ OutputFormat::Json => Box::new(JsonFormatter::new()),
+ OutputFormat::Rst => Box::new(RstFormatter::new()),
+ OutputFormat::Shall => Box::new(ShallFormatter::new()),
+ }
+}
\ No newline at end of file
diff --git a/tools/kapi/src/formatter/plain.rs b/tools/kapi/src/formatter/plain.rs
new file mode 100644
index 0000000000000..cc78026f20dd1
--- /dev/null
+++ b/tools/kapi/src/formatter/plain.rs
@@ -0,0 +1,465 @@
+use super::OutputFormatter;
+use std::io::Write;
+use crate::extractor::{SocketStateSpec, ProtocolBehaviorSpec, AddrFamilySpec, BufferSpec, AsyncSpec, CapabilitySpec,
+ ParamSpec, ReturnSpec, ErrorSpec, SignalSpec, SignalMaskSpec, SideEffectSpec, StateTransitionSpec, ConstraintSpec, LockSpec};
+
+pub struct PlainFormatter;
+
+impl PlainFormatter {
+ pub fn new() -> Self {
+ PlainFormatter
+ }
+}
+
+impl OutputFormatter for PlainFormatter {
+ fn begin_document(&mut self, _w: &mut dyn Write) -> std::io::Result<()> {
+ Ok(())
+ }
+
+ fn end_document(&mut self, _w: &mut dyn Write) -> std::io::Result<()> {
+ Ok(())
+ }
+
+ fn begin_api_list(&mut self, w: &mut dyn Write, title: &str) -> std::io::Result<()> {
+ writeln!(w, "\n{title}:")?;
+ writeln!(w, "{}", "-".repeat(title.len() + 1))
+ }
+
+ fn api_item(&mut self, w: &mut dyn Write, name: &str, _api_type: &str) -> std::io::Result<()> {
+ writeln!(w, " {name}")
+ }
+
+ fn end_api_list(&mut self, _w: &mut dyn Write) -> std::io::Result<()> {
+ Ok(())
+ }
+
+ fn total_specs(&mut self, w: &mut dyn Write, count: usize) -> std::io::Result<()> {
+ writeln!(w, "\nTotal specifications found: {count}")
+ }
+
+ fn begin_api_details(&mut self, w: &mut dyn Write, name: &str) -> std::io::Result<()> {
+ writeln!(w, "\nDetailed information for {name}:")?;
+ writeln!(w, "{}=", "=".repeat(25 + name.len()))
+ }
+
+ fn end_api_details(&mut self, _w: &mut dyn Write) -> std::io::Result<()> {
+ Ok(())
+ }
+
+
+ fn description(&mut self, w: &mut dyn Write, desc: &str) -> std::io::Result<()> {
+ writeln!(w, "Description: {desc}")
+ }
+
+ fn long_description(&mut self, w: &mut dyn Write, desc: &str) -> std::io::Result<()> {
+ writeln!(w, "\nDetailed Description:")?;
+ writeln!(w, "{desc}")
+ }
+
+ fn begin_context_flags(&mut self, w: &mut dyn Write) -> std::io::Result<()> {
+ writeln!(w, "\nExecution Context:")
+ }
+
+ fn context_flag(&mut self, w: &mut dyn Write, flag: &str) -> std::io::Result<()> {
+ writeln!(w, " - {flag}")
+ }
+
+ fn end_context_flags(&mut self, _w: &mut dyn Write) -> std::io::Result<()> {
+ Ok(())
+ }
+
+ fn begin_parameters(&mut self, w: &mut dyn Write, count: u32) -> std::io::Result<()> {
+ writeln!(w, "\nParameters ({count}):")
+ }
+
+ fn parameter(&mut self, w: &mut dyn Write, param: &ParamSpec) -> std::io::Result<()> {
+ writeln!(w, " [{}] {} ({})", param.index, param.name, param.type_name)?;
+ if !param.description.is_empty() {
+ writeln!(w, " {}", param.description)?;
+ }
+
+ // Display flags
+ let mut flags = Vec::new();
+ if param.flags & 0x01 != 0 { flags.push("IN"); }
+ if param.flags & 0x02 != 0 { flags.push("OUT"); }
+ if param.flags & 0x04 != 0 { flags.push("INOUT"); }
+ if param.flags & 0x08 != 0 { flags.push("USER"); }
+ if param.flags & 0x10 != 0 { flags.push("OPTIONAL"); }
+ if !flags.is_empty() {
+ writeln!(w, " Flags: {}", flags.join(" | "))?;
+ }
+
+ // Display constraints
+ if let Some(constraint) = ¶m.constraint {
+ writeln!(w, " Constraint: {constraint}")?;
+ }
+ if let (Some(min), Some(max)) = (param.min_value, param.max_value) {
+ writeln!(w, " Range: {min} to {max}")?;
+ }
+ if let Some(mask) = param.valid_mask {
+ writeln!(w, " Valid mask: 0x{mask:x}")?;
+ }
+ Ok(())
+ }
+
+ fn end_parameters(&mut self, _w: &mut dyn Write) -> std::io::Result<()> {
+ Ok(())
+ }
+
+ fn return_spec(&mut self, w: &mut dyn Write, ret: &ReturnSpec) -> std::io::Result<()> {
+ writeln!(w, "\nReturn Value:")?;
+ writeln!(w, " Type: {}", ret.type_name)?;
+ writeln!(w, " {}", ret.description)?;
+ if let Some(val) = ret.success_value {
+ writeln!(w, " Success value: {val}")?;
+ }
+ if let (Some(min), Some(max)) = (ret.success_min, ret.success_max) {
+ writeln!(w, " Success range: {min} to {max}")?;
+ }
+ Ok(())
+ }
+
+ fn begin_errors(&mut self, w: &mut dyn Write, count: u32) -> std::io::Result<()> {
+ writeln!(w, "\nPossible Errors ({count}):")
+ }
+
+ fn error(&mut self, w: &mut dyn Write, error: &ErrorSpec) -> std::io::Result<()> {
+ writeln!(w, " {} ({})", error.name, error.error_code)?;
+ if !error.condition.is_empty() {
+ writeln!(w, " Condition: {}", error.condition)?;
+ }
+ if !error.description.is_empty() {
+ writeln!(w, " {}", error.description)?;
+ }
+ Ok(())
+ }
+
+ fn end_errors(&mut self, _w: &mut dyn Write) -> std::io::Result<()> {
+ Ok(())
+ }
+
+ fn examples(&mut self, w: &mut dyn Write, examples: &str) -> std::io::Result<()> {
+ writeln!(w, "\nExamples:")?;
+ writeln!(w, "{examples}")
+ }
+
+ fn notes(&mut self, w: &mut dyn Write, notes: &str) -> std::io::Result<()> {
+ writeln!(w, "\nNotes:")?;
+ writeln!(w, "{notes}")
+ }
+
+ fn since_version(&mut self, w: &mut dyn Write, version: &str) -> std::io::Result<()> {
+ writeln!(w, "\nAvailable since: {version}")
+ }
+
+ fn sysfs_subsystem(&mut self, w: &mut dyn Write, subsystem: &str) -> std::io::Result<()> {
+ writeln!(w, "Subsystem: {subsystem}")
+ }
+
+ fn sysfs_path(&mut self, w: &mut dyn Write, path: &str) -> std::io::Result<()> {
+ writeln!(w, "Sysfs Path: {path}")
+ }
+
+ fn sysfs_permissions(&mut self, w: &mut dyn Write, perms: &str) -> std::io::Result<()> {
+ writeln!(w, "Permissions: {perms}")
+ }
+
+ // Networking-specific methods
+ fn socket_state(&mut self, w: &mut dyn Write, state: &SocketStateSpec) -> std::io::Result<()> {
+ writeln!(w, "\nSocket State Requirements:")?;
+ if !state.required_states.is_empty() {
+ writeln!(w, " Required states: {:?}", state.required_states)?;
+ }
+ if !state.forbidden_states.is_empty() {
+ writeln!(w, " Forbidden states: {:?}", state.forbidden_states)?;
+ }
+ if let Some(result) = &state.resulting_state {
+ writeln!(w, " Resulting state: {result}")?;
+ }
+ if let Some(cond) = &state.condition {
+ writeln!(w, " Condition: {cond}")?;
+ }
+ if let Some(protos) = &state.applicable_protocols {
+ writeln!(w, " Applicable protocols: {protos}")?;
+ }
+ Ok(())
+ }
+
+ fn begin_protocol_behaviors(&mut self, w: &mut dyn Write) -> std::io::Result<()> {
+ writeln!(w, "\nProtocol-Specific Behaviors:")
+ }
+
+ fn protocol_behavior(&mut self, w: &mut dyn Write, behavior: &ProtocolBehaviorSpec) -> std::io::Result<()> {
+ writeln!(w, " {} - {}", behavior.applicable_protocols, behavior.behavior)?;
+ if let Some(flags) = &behavior.protocol_flags {
+ writeln!(w, " Flags: {flags}")?;
+ }
+ Ok(())
+ }
+
+ fn end_protocol_behaviors(&mut self, _w: &mut dyn Write) -> std::io::Result<()> {
+ Ok(())
+ }
+
+ fn begin_addr_families(&mut self, w: &mut dyn Write) -> std::io::Result<()> {
+ writeln!(w, "\nSupported Address Families:")
+ }
+
+ fn addr_family(&mut self, w: &mut dyn Write, family: &AddrFamilySpec) -> std::io::Result<()> {
+ writeln!(w, " {} ({}):", family.family_name, family.family)?;
+ writeln!(w, " Struct size: {} bytes", family.addr_struct_size)?;
+ writeln!(w, " Address length: {}-{} bytes", family.min_addr_len, family.max_addr_len)?;
+ if let Some(format) = &family.addr_format {
+ writeln!(w, " Format: {format}")?;
+ }
+ writeln!(w, " Features: wildcard={}, multicast={}, broadcast={}",
+ family.supports_wildcard, family.supports_multicast, family.supports_broadcast)?;
+ if let Some(special) = &family.special_addresses {
+ writeln!(w, " Special addresses: {special}")?;
+ }
+ if family.port_range_max > 0 {
+ writeln!(w, " Port range: {}-{}", family.port_range_min, family.port_range_max)?;
+ }
+ Ok(())
+ }
+
+ fn end_addr_families(&mut self, _w: &mut dyn Write) -> std::io::Result<()> {
+ Ok(())
+ }
+
+ fn buffer_spec(&mut self, w: &mut dyn Write, spec: &BufferSpec) -> std::io::Result<()> {
+ writeln!(w, "\nBuffer Specification:")?;
+ if let Some(behaviors) = &spec.buffer_behaviors {
+ writeln!(w, " Behaviors: {behaviors}")?;
+ }
+ if let Some(min) = spec.min_buffer_size {
+ writeln!(w, " Min size: {min} bytes")?;
+ }
+ if let Some(max) = spec.max_buffer_size {
+ writeln!(w, " Max size: {max} bytes")?;
+ }
+ if let Some(optimal) = spec.optimal_buffer_size {
+ writeln!(w, " Optimal size: {optimal} bytes")?;
+ }
+ Ok(())
+ }
+
+ fn async_spec(&mut self, w: &mut dyn Write, spec: &AsyncSpec) -> std::io::Result<()> {
+ writeln!(w, "\nAsynchronous Operation:")?;
+ if let Some(modes) = &spec.supported_modes {
+ writeln!(w, " Supported modes: {modes}")?;
+ }
+ if let Some(errno) = spec.nonblock_errno {
+ writeln!(w, " Non-blocking errno: {errno}")?;
+ }
+ Ok(())
+ }
+
+ fn net_data_transfer(&mut self, w: &mut dyn Write, desc: &str) -> std::io::Result<()> {
+ writeln!(w, "\nNetwork Data Transfer: {desc}")
+ }
+
+ fn begin_capabilities(&mut self, w: &mut dyn Write) -> std::io::Result<()> {
+ writeln!(w, "\nRequired Capabilities:")
+ }
+
+ fn capability(&mut self, w: &mut dyn Write, cap: &CapabilitySpec) -> std::io::Result<()> {
+ writeln!(w, " {} ({}) - {}", cap.name, cap.capability, cap.action)?;
+ if !cap.allows.is_empty() {
+ writeln!(w, " Allows: {}", cap.allows)?;
+ }
+ if !cap.without_cap.is_empty() {
+ writeln!(w, " Without capability: {}", cap.without_cap)?;
+ }
+ if let Some(cond) = &cap.check_condition {
+ writeln!(w, " Condition: {cond}")?;
+ }
+ Ok(())
+ }
+
+ fn end_capabilities(&mut self, _w: &mut dyn Write) -> std::io::Result<()> {
+ Ok(())
+ }
+
+ // Signal-related methods
+ fn begin_signals(&mut self, w: &mut dyn Write, count: u32) -> std::io::Result<()> {
+ writeln!(w, "\nSignal Specifications ({count}):")
+ }
+
+ fn signal(&mut self, w: &mut dyn Write, signal: &SignalSpec) -> std::io::Result<()> {
+ write!(w, " {} ({})", signal.signal_name, signal.signal_num)?;
+
+ // Display direction
+ let direction = match signal.direction {
+ 0 => "SEND",
+ 1 => "RECEIVE",
+ 2 => "HANDLE",
+ 3 => "IGNORE",
+ _ => "UNKNOWN",
+ };
+ write!(w, " - {direction}")?;
+
+ // Display action
+ let action = match signal.action {
+ 0 => "DEFAULT",
+ 1 => "TERMINATE",
+ 2 => "COREDUMP",
+ 3 => "STOP",
+ 4 => "CONTINUE",
+ 5 => "IGNORE",
+ 6 => "CUSTOM",
+ 7 => "DISCARD",
+ _ => "UNKNOWN",
+ };
+ writeln!(w, " - {action}")?;
+
+ if let Some(target) = &signal.target {
+ writeln!(w, " Target: {target}")?;
+ }
+ if let Some(condition) = &signal.condition {
+ writeln!(w, " Condition: {condition}")?;
+ }
+ if let Some(desc) = &signal.description {
+ writeln!(w, " {desc}")?;
+ }
+
+ // Display timing
+ let timing = match signal.timing {
+ 0 => "BEFORE",
+ 1 => "DURING",
+ 2 => "AFTER",
+ 3 => "EXIT",
+ _ => "UNKNOWN",
+ };
+ writeln!(w, " Timing: {timing}")?;
+ writeln!(w, " Priority: {}", signal.priority)?;
+
+ if signal.restartable {
+ writeln!(w, " Restartable: yes")?;
+ }
+ if signal.interruptible {
+ writeln!(w, " Interruptible: yes")?;
+ }
+ if let Some(error) = signal.error_on_signal {
+ writeln!(w, " Error on signal: {error}")?;
+ }
+ Ok(())
+ }
+
+ fn end_signals(&mut self, _w: &mut dyn Write) -> std::io::Result<()> {
+ Ok(())
+ }
+
+ fn begin_signal_masks(&mut self, w: &mut dyn Write, count: u32) -> std::io::Result<()> {
+ writeln!(w, "\nSignal Masks ({count}):")
+ }
+
+ fn signal_mask(&mut self, w: &mut dyn Write, mask: &SignalMaskSpec) -> std::io::Result<()> {
+ writeln!(w, " {}", mask.name)?;
+ if !mask.description.is_empty() {
+ writeln!(w, " {}", mask.description)?;
+ }
+ Ok(())
+ }
+
+ fn end_signal_masks(&mut self, _w: &mut dyn Write) -> std::io::Result<()> {
+ Ok(())
+ }
+
+ // Side effects and state transitions
+ fn begin_side_effects(&mut self, w: &mut dyn Write, count: u32) -> std::io::Result<()> {
+ writeln!(w, "\nSide Effects ({count}):")
+ }
+
+ fn side_effect(&mut self, w: &mut dyn Write, effect: &SideEffectSpec) -> std::io::Result<()> {
+ writeln!(w, " {} - {}", effect.target, effect.description)?;
+ if let Some(condition) = &effect.condition {
+ writeln!(w, " Condition: {condition}")?;
+ }
+ if effect.reversible {
+ writeln!(w, " Reversible: yes")?;
+ }
+ Ok(())
+ }
+
+ fn end_side_effects(&mut self, _w: &mut dyn Write) -> std::io::Result<()> {
+ Ok(())
+ }
+
+ fn begin_state_transitions(&mut self, w: &mut dyn Write, count: u32) -> std::io::Result<()> {
+ writeln!(w, "\nState Transitions ({count}):")
+ }
+
+ fn state_transition(&mut self, w: &mut dyn Write, trans: &StateTransitionSpec) -> std::io::Result<()> {
+ writeln!(w, " {} : {} -> {}", trans.object, trans.from_state, trans.to_state)?;
+ if let Some(condition) = &trans.condition {
+ writeln!(w, " Condition: {condition}")?;
+ }
+ if !trans.description.is_empty() {
+ writeln!(w, " {}", trans.description)?;
+ }
+ Ok(())
+ }
+
+ fn end_state_transitions(&mut self, _w: &mut dyn Write) -> std::io::Result<()> {
+ Ok(())
+ }
+
+ // Constraints and locks
+ fn begin_constraints(&mut self, w: &mut dyn Write, count: u32) -> std::io::Result<()> {
+ writeln!(w, "\nAdditional Constraints ({count}):")
+ }
+
+ fn constraint(&mut self, w: &mut dyn Write, constraint: &ConstraintSpec) -> std::io::Result<()> {
+ writeln!(w, " {}", constraint.name)?;
+ if !constraint.description.is_empty() {
+ writeln!(w, " {}", constraint.description)?;
+ }
+ if let Some(expr) = &constraint.expression {
+ writeln!(w, " Expression: {expr}")?;
+ }
+ Ok(())
+ }
+
+ fn end_constraints(&mut self, _w: &mut dyn Write) -> std::io::Result<()> {
+ Ok(())
+ }
+
+ fn begin_locks(&mut self, w: &mut dyn Write, count: u32) -> std::io::Result<()> {
+ writeln!(w, "\nLocking Requirements ({count}):")
+ }
+
+ fn lock(&mut self, w: &mut dyn Write, lock: &LockSpec) -> std::io::Result<()> {
+ write!(w, " {}", lock.lock_name)?;
+
+ // Display lock type
+ let lock_type = match lock.lock_type {
+ 0 => "SPINLOCK",
+ 1 => "MUTEX",
+ 2 => "RWLOCK",
+ 3 => "SEMAPHORE",
+ 4 => "RCU",
+ _ => "UNKNOWN",
+ };
+ writeln!(w, " ({lock_type})")?;
+
+ let mut actions = Vec::new();
+ if lock.acquired { actions.push("acquired"); }
+ if lock.released { actions.push("released"); }
+ if lock.held_on_entry { actions.push("held on entry"); }
+ if lock.held_on_exit { actions.push("held on exit"); }
+
+ if !actions.is_empty() {
+ writeln!(w, " Actions: {}", actions.join(", "))?;
+ }
+
+ if !lock.description.is_empty() {
+ writeln!(w, " {}", lock.description)?;
+ }
+ Ok(())
+ }
+
+ fn end_locks(&mut self, _w: &mut dyn Write) -> std::io::Result<()> {
+ Ok(())
+ }
+}
\ No newline at end of file
diff --git a/tools/kapi/src/formatter/rst.rs b/tools/kapi/src/formatter/rst.rs
new file mode 100644
index 0000000000000..ee660af176781
--- /dev/null
+++ b/tools/kapi/src/formatter/rst.rs
@@ -0,0 +1,468 @@
+use super::OutputFormatter;
+use std::io::Write;
+use crate::extractor::{SocketStateSpec, ProtocolBehaviorSpec, AddrFamilySpec, BufferSpec, AsyncSpec, CapabilitySpec,
+ ParamSpec, ReturnSpec, ErrorSpec, SignalSpec, SignalMaskSpec, SideEffectSpec, StateTransitionSpec, ConstraintSpec, LockSpec};
+
+pub struct RstFormatter {
+ current_section_level: usize,
+}
+
+impl RstFormatter {
+ pub fn new() -> Self {
+ RstFormatter {
+ current_section_level: 0,
+ }
+ }
+
+ fn section_char(level: usize) -> char {
+ match level {
+ 0 => '=',
+ 1 => '-',
+ 2 => '~',
+ 3 => '^',
+ _ => '"',
+ }
+ }
+}
+
+impl OutputFormatter for RstFormatter {
+ fn begin_document(&mut self, _w: &mut dyn Write) -> std::io::Result<()> {
+ Ok(())
+ }
+
+ fn end_document(&mut self, _w: &mut dyn Write) -> std::io::Result<()> {
+ Ok(())
+ }
+
+ fn begin_api_list(&mut self, w: &mut dyn Write, title: &str) -> std::io::Result<()> {
+ writeln!(w, "\n{title}")?;
+ writeln!(w, "{}", Self::section_char(0).to_string().repeat(title.len()))?;
+ writeln!(w)
+ }
+
+ fn api_item(&mut self, w: &mut dyn Write, name: &str, api_type: &str) -> std::io::Result<()> {
+ writeln!(w, "* **{name}** (*{api_type}*)")
+ }
+
+ fn end_api_list(&mut self, _w: &mut dyn Write) -> std::io::Result<()> {
+ Ok(())
+ }
+
+ fn total_specs(&mut self, w: &mut dyn Write, count: usize) -> std::io::Result<()> {
+ writeln!(w, "\n**Total specifications found:** {count}")
+ }
+
+ fn begin_api_details(&mut self, w: &mut dyn Write, name: &str) -> std::io::Result<()> {
+ self.current_section_level = 0;
+ writeln!(w, "\n{name}")?;
+ writeln!(w, "{}", Self::section_char(0).to_string().repeat(name.len()))?;
+ writeln!(w)
+ }
+
+ fn end_api_details(&mut self, _w: &mut dyn Write) -> std::io::Result<()> {
+ Ok(())
+ }
+
+
+ fn description(&mut self, w: &mut dyn Write, desc: &str) -> std::io::Result<()> {
+ writeln!(w, "**{desc}**")?;
+ writeln!(w)
+ }
+
+ fn long_description(&mut self, w: &mut dyn Write, desc: &str) -> std::io::Result<()> {
+ writeln!(w, "{desc}")?;
+ writeln!(w)
+ }
+
+ fn begin_context_flags(&mut self, w: &mut dyn Write) -> std::io::Result<()> {
+ self.current_section_level = 1;
+ let title = "Execution Context";
+ writeln!(w, "{title}")?;
+ writeln!(w, "{}", Self::section_char(1).to_string().repeat(title.len()))?;
+ writeln!(w)
+ }
+
+ fn context_flag(&mut self, w: &mut dyn Write, flag: &str) -> std::io::Result<()> {
+ writeln!(w, "* {flag}")
+ }
+
+ fn end_context_flags(&mut self, w: &mut dyn Write) -> std::io::Result<()> {
+ writeln!(w)
+ }
+
+ fn begin_parameters(&mut self, w: &mut dyn Write, count: u32) -> std::io::Result<()> {
+ self.current_section_level = 1;
+ let title = format!("Parameters ({count})");
+ writeln!(w, "{title}")?;
+ writeln!(w, "{}", Self::section_char(1).to_string().repeat(title.len()))?;
+ writeln!(w)
+ }
+
+
+ fn end_parameters(&mut self, _w: &mut dyn Write) -> std::io::Result<()> {
+ Ok(())
+ }
+
+ fn begin_errors(&mut self, w: &mut dyn Write, count: u32) -> std::io::Result<()> {
+ self.current_section_level = 1;
+ let title = format!("Possible Errors ({count})");
+ writeln!(w, "{title}")?;
+ writeln!(w, "{}", Self::section_char(1).to_string().repeat(title.len()))?;
+ writeln!(w)
+ }
+
+ fn end_errors(&mut self, _w: &mut dyn Write) -> std::io::Result<()> {
+ Ok(())
+ }
+
+ fn examples(&mut self, w: &mut dyn Write, examples: &str) -> std::io::Result<()> {
+ self.current_section_level = 1;
+ let title = "Examples";
+ writeln!(w, "{title}")?;
+ writeln!(w, "{}", Self::section_char(1).to_string().repeat(title.len()))?;
+ writeln!(w)?;
+ writeln!(w, ".. code-block:: c")?;
+ writeln!(w)?;
+ for line in examples.lines() {
+ writeln!(w, " {line}")?;
+ }
+ writeln!(w)
+ }
+
+ fn notes(&mut self, w: &mut dyn Write, notes: &str) -> std::io::Result<()> {
+ self.current_section_level = 1;
+ let title = "Notes";
+ writeln!(w, "{title}")?;
+ writeln!(w, "{}", Self::section_char(1).to_string().repeat(title.len()))?;
+ writeln!(w)?;
+ writeln!(w, "{notes}")?;
+ writeln!(w)
+ }
+
+ fn since_version(&mut self, w: &mut dyn Write, version: &str) -> std::io::Result<()> {
+ writeln!(w, ":Available since: {version}")?;
+ writeln!(w)
+ }
+
+ fn sysfs_subsystem(&mut self, w: &mut dyn Write, subsystem: &str) -> std::io::Result<()> {
+ writeln!(w, ":Subsystem: {subsystem}")?;
+ writeln!(w)
+ }
+
+ fn sysfs_path(&mut self, w: &mut dyn Write, path: &str) -> std::io::Result<()> {
+ writeln!(w, ":Sysfs Path: {path}")?;
+ writeln!(w)
+ }
+
+ fn sysfs_permissions(&mut self, w: &mut dyn Write, perms: &str) -> std::io::Result<()> {
+ writeln!(w, ":Permissions: {perms}")?;
+ writeln!(w)
+ }
+
+ // Networking-specific methods
+ fn socket_state(&mut self, w: &mut dyn Write, state: &SocketStateSpec) -> std::io::Result<()> {
+ self.current_section_level = 1;
+ let title = "Socket State Requirements";
+ writeln!(w, "{title}")?;
+ writeln!(w, "{}", Self::section_char(1).to_string().repeat(title.len()))?;
+ writeln!(w)?;
+
+ if !state.required_states.is_empty() {
+ writeln!(w, "**Required states:** {}", state.required_states.join(", "))?;
+ }
+ if !state.forbidden_states.is_empty() {
+ writeln!(w, "**Forbidden states:** {}", state.forbidden_states.join(", "))?;
+ }
+ if let Some(result) = &state.resulting_state {
+ writeln!(w, "**Resulting state:** {result}")?;
+ }
+ if let Some(cond) = &state.condition {
+ writeln!(w, "**Condition:** {cond}")?;
+ }
+ if let Some(protos) = &state.applicable_protocols {
+ writeln!(w, "**Applicable protocols:** {protos}")?;
+ }
+ writeln!(w)
+ }
+
+ fn begin_protocol_behaviors(&mut self, w: &mut dyn Write) -> std::io::Result<()> {
+ self.current_section_level = 1;
+ let title = "Protocol-Specific Behaviors";
+ writeln!(w, "{title}")?;
+ writeln!(w, "{}", Self::section_char(1).to_string().repeat(title.len()))?;
+ writeln!(w)
+ }
+
+ fn protocol_behavior(&mut self, w: &mut dyn Write, behavior: &ProtocolBehaviorSpec) -> std::io::Result<()> {
+ writeln!(w, "**{}**", behavior.applicable_protocols)?;
+ writeln!(w)?;
+ writeln!(w, "{}", behavior.behavior)?;
+ if let Some(flags) = &behavior.protocol_flags {
+ writeln!(w)?;
+ writeln!(w, "*Flags:* {flags}")?;
+ }
+ writeln!(w)
+ }
+
+ fn end_protocol_behaviors(&mut self, _w: &mut dyn Write) -> std::io::Result<()> {
+ Ok(())
+ }
+
+ fn begin_addr_families(&mut self, w: &mut dyn Write) -> std::io::Result<()> {
+ self.current_section_level = 1;
+ let title = "Supported Address Families";
+ writeln!(w, "{title}")?;
+ writeln!(w, "{}", Self::section_char(1).to_string().repeat(title.len()))?;
+ writeln!(w)
+ }
+
+ fn addr_family(&mut self, w: &mut dyn Write, family: &AddrFamilySpec) -> std::io::Result<()> {
+ writeln!(w, "**{} ({})**", family.family_name, family.family)?;
+ writeln!(w)?;
+ writeln!(w, "* **Struct size:** {} bytes", family.addr_struct_size)?;
+ writeln!(w, "* **Address length:** {}-{} bytes", family.min_addr_len, family.max_addr_len)?;
+ if let Some(format) = &family.addr_format {
+ writeln!(w, "* **Format:** ``{format}``")?;
+ }
+ writeln!(w, "* **Features:** wildcard={}, multicast={}, broadcast={}",
+ family.supports_wildcard, family.supports_multicast, family.supports_broadcast)?;
+ if let Some(special) = &family.special_addresses {
+ writeln!(w, "* **Special addresses:** {special}")?;
+ }
+ if family.port_range_max > 0 {
+ writeln!(w, "* **Port range:** {}-{}", family.port_range_min, family.port_range_max)?;
+ }
+ writeln!(w)
+ }
+
+ fn end_addr_families(&mut self, _w: &mut dyn Write) -> std::io::Result<()> {
+ Ok(())
+ }
+
+ fn buffer_spec(&mut self, w: &mut dyn Write, spec: &BufferSpec) -> std::io::Result<()> {
+ self.current_section_level = 1;
+ let title = "Buffer Specification";
+ writeln!(w, "{title}")?;
+ writeln!(w, "{}", Self::section_char(1).to_string().repeat(title.len()))?;
+ writeln!(w)?;
+
+ if let Some(behaviors) = &spec.buffer_behaviors {
+ writeln!(w, "**Behaviors:** {behaviors}")?;
+ }
+ if let Some(min) = spec.min_buffer_size {
+ writeln!(w, "**Min size:** {min} bytes")?;
+ }
+ if let Some(max) = spec.max_buffer_size {
+ writeln!(w, "**Max size:** {max} bytes")?;
+ }
+ if let Some(optimal) = spec.optimal_buffer_size {
+ writeln!(w, "**Optimal size:** {optimal} bytes")?;
+ }
+ writeln!(w)
+ }
+
+ fn async_spec(&mut self, w: &mut dyn Write, spec: &AsyncSpec) -> std::io::Result<()> {
+ self.current_section_level = 1;
+ let title = "Asynchronous Operation";
+ writeln!(w, "{title}")?;
+ writeln!(w, "{}", Self::section_char(1).to_string().repeat(title.len()))?;
+ writeln!(w)?;
+
+ if let Some(modes) = &spec.supported_modes {
+ writeln!(w, "**Supported modes:** {modes}")?;
+ }
+ if let Some(errno) = spec.nonblock_errno {
+ writeln!(w, "**Non-blocking errno:** {errno}")?;
+ }
+ writeln!(w)
+ }
+
+ fn net_data_transfer(&mut self, w: &mut dyn Write, desc: &str) -> std::io::Result<()> {
+ writeln!(w, "**Network Data Transfer:** {desc}")?;
+ writeln!(w)
+ }
+
+ fn begin_capabilities(&mut self, w: &mut dyn Write) -> std::io::Result<()> {
+ self.current_section_level = 1;
+ let title = "Required Capabilities";
+ writeln!(w, "{title}")?;
+ writeln!(w, "{}", Self::section_char(1).to_string().repeat(title.len()))?;
+ writeln!(w)
+ }
+
+ fn capability(&mut self, w: &mut dyn Write, cap: &CapabilitySpec) -> std::io::Result<()> {
+ writeln!(w, "**{} ({})** - {}", cap.name, cap.capability, cap.action)?;
+ writeln!(w)?;
+ if !cap.allows.is_empty() {
+ writeln!(w, "* **Allows:** {}", cap.allows)?;
+ }
+ if !cap.without_cap.is_empty() {
+ writeln!(w, "* **Without capability:** {}", cap.without_cap)?;
+ }
+ if let Some(cond) = &cap.check_condition {
+ writeln!(w, "* **Condition:** {}", cond)?;
+ }
+ writeln!(w)
+ }
+
+ fn end_capabilities(&mut self, _w: &mut dyn Write) -> std::io::Result<()> {
+ Ok(())
+ }
+
+ // Stub implementations for new methods
+ fn parameter(&mut self, w: &mut dyn Write, param: &ParamSpec) -> std::io::Result<()> {
+ writeln!(w, "**[{}] {}** (*{}*)", param.index, param.name, param.type_name)?;
+ writeln!(w)?;
+ writeln!(w, " {}", param.description)?;
+
+ // Display flags
+ let mut flags = Vec::new();
+ if param.flags & 0x01 != 0 { flags.push("IN"); }
+ if param.flags & 0x02 != 0 { flags.push("OUT"); }
+ if param.flags & 0x04 != 0 { flags.push("USER"); }
+ if param.flags & 0x08 != 0 { flags.push("OPTIONAL"); }
+ if !flags.is_empty() {
+ writeln!(w, " :Flags: {}", flags.join(", "))?;
+ }
+
+ if let Some(constraint) = ¶m.constraint {
+ writeln!(w, " :Constraint: {}", constraint)?;
+ }
+
+ if let (Some(min), Some(max)) = (param.min_value, param.max_value) {
+ writeln!(w, " :Range: {} to {}", min, max)?;
+ }
+
+ writeln!(w)
+ }
+
+ fn return_spec(&mut self, w: &mut dyn Write, ret: &ReturnSpec) -> std::io::Result<()> {
+ writeln!(w, "\nReturn Value")?;
+ writeln!(w, "{}\n", Self::section_char(1).to_string().repeat(12))?;
+ writeln!(w)?;
+ writeln!(w, ":Type: {}", ret.type_name)?;
+ writeln!(w, ":Description: {}", ret.description)?;
+ if let Some(success) = ret.success_value {
+ writeln!(w, ":Success value: {}", success)?;
+ }
+ writeln!(w)
+ }
+
+ fn error(&mut self, w: &mut dyn Write, error: &ErrorSpec) -> std::io::Result<()> {
+ writeln!(w, "**{}** ({})", error.name, error.error_code)?;
+ writeln!(w)?;
+ writeln!(w, " :Condition: {}", error.condition)?;
+ if !error.description.is_empty() {
+ writeln!(w, " :Description: {}", error.description)?;
+ }
+ writeln!(w)
+ }
+
+ fn begin_signals(&mut self, _w: &mut dyn Write, _count: u32) -> std::io::Result<()> {
+ Ok(())
+ }
+
+ fn signal(&mut self, _w: &mut dyn Write, _signal: &SignalSpec) -> std::io::Result<()> {
+ Ok(())
+ }
+
+ fn end_signals(&mut self, _w: &mut dyn Write) -> std::io::Result<()> {
+ Ok(())
+ }
+
+ fn begin_signal_masks(&mut self, _w: &mut dyn Write, _count: u32) -> std::io::Result<()> {
+ Ok(())
+ }
+
+ fn signal_mask(&mut self, _w: &mut dyn Write, _mask: &SignalMaskSpec) -> std::io::Result<()> {
+ Ok(())
+ }
+
+ fn end_signal_masks(&mut self, _w: &mut dyn Write) -> std::io::Result<()> {
+ Ok(())
+ }
+
+ fn begin_side_effects(&mut self, w: &mut dyn Write, count: u32) -> std::io::Result<()> {
+ self.current_section_level = 1;
+ let title = format!("Side Effects ({count})");
+ writeln!(w, "{}\n", title)?;
+ writeln!(w, "{}\n", Self::section_char(1).to_string().repeat(title.len()))
+ }
+
+ fn side_effect(&mut self, w: &mut dyn Write, effect: &SideEffectSpec) -> std::io::Result<()> {
+ write!(w, "* **{}**", effect.target)?;
+ if effect.reversible {
+ write!(w, " *(reversible)*")?;
+ }
+ writeln!(w)?;
+ writeln!(w, " {}", effect.description)?;
+ if let Some(cond) = &effect.condition {
+ writeln!(w, " :Condition: {}", cond)?;
+ }
+ writeln!(w)
+ }
+
+ fn end_side_effects(&mut self, _w: &mut dyn Write) -> std::io::Result<()> {
+ Ok(())
+ }
+
+ fn begin_state_transitions(&mut self, w: &mut dyn Write, count: u32) -> std::io::Result<()> {
+ self.current_section_level = 1;
+ let title = format!("State Transitions ({count})");
+ writeln!(w, "{}\n", title)?;
+ writeln!(w, "{}\n", Self::section_char(1).to_string().repeat(title.len()))
+ }
+
+ fn state_transition(&mut self, w: &mut dyn Write, trans: &StateTransitionSpec) -> std::io::Result<()> {
+ writeln!(w, "* **{}**: {} → {}", trans.object, trans.from_state, trans.to_state)?;
+ writeln!(w, " {}", trans.description)?;
+ if let Some(cond) = &trans.condition {
+ writeln!(w, " :Condition: {}", cond)?;
+ }
+ writeln!(w)
+ }
+
+ fn end_state_transitions(&mut self, _w: &mut dyn Write) -> std::io::Result<()> {
+ Ok(())
+ }
+
+ fn begin_constraints(&mut self, _w: &mut dyn Write, _count: u32) -> std::io::Result<()> {
+ Ok(())
+ }
+
+ fn constraint(&mut self, _w: &mut dyn Write, _constraint: &ConstraintSpec) -> std::io::Result<()> {
+ Ok(())
+ }
+
+ fn end_constraints(&mut self, _w: &mut dyn Write) -> std::io::Result<()> {
+ Ok(())
+ }
+
+ fn begin_locks(&mut self, w: &mut dyn Write, count: u32) -> std::io::Result<()> {
+ self.current_section_level = 1;
+ let title = format!("Locks ({count})");
+ writeln!(w, "{}\n", title)?;
+ writeln!(w, "{}\n", Self::section_char(1).to_string().repeat(title.len()))
+ }
+
+ fn lock(&mut self, w: &mut dyn Write, lock: &LockSpec) -> std::io::Result<()> {
+ write!(w, "* **{}**", lock.lock_name)?;
+ let lock_type_str = match lock.lock_type {
+ 1 => " *(mutex)*",
+ 2 => " *(spinlock)*",
+ 3 => " *(rwlock)*",
+ 4 => " *(semaphore)*",
+ 5 => " *(RCU)*",
+ _ => "",
+ };
+ writeln!(w, "{}", lock_type_str)?;
+ if !lock.description.is_empty() {
+ writeln!(w, " {}", lock.description)?;
+ }
+ writeln!(w)
+ }
+
+ fn end_locks(&mut self, _w: &mut dyn Write) -> std::io::Result<()> {
+ Ok(())
+ }
+}
\ No newline at end of file
diff --git a/tools/kapi/src/formatter/shall.rs b/tools/kapi/src/formatter/shall.rs
new file mode 100644
index 0000000000000..ef432a060da52
--- /dev/null
+++ b/tools/kapi/src/formatter/shall.rs
@@ -0,0 +1,605 @@
+use super::OutputFormatter;
+use std::io::Write;
+use crate::extractor::{SocketStateSpec, ProtocolBehaviorSpec, AddrFamilySpec, BufferSpec, AsyncSpec, CapabilitySpec,
+ ParamSpec, ReturnSpec, ErrorSpec, SignalSpec, SignalMaskSpec, SideEffectSpec, StateTransitionSpec, ConstraintSpec, LockSpec};
+
+pub struct ShallFormatter {
+ api_name: Option<String>,
+ in_list: bool,
+}
+
+impl ShallFormatter {
+ pub fn new() -> Self {
+ ShallFormatter {
+ api_name: None,
+ in_list: false,
+ }
+ }
+
+}
+
+impl OutputFormatter for ShallFormatter {
+ fn begin_document(&mut self, _w: &mut dyn Write) -> std::io::Result<()> {
+ Ok(())
+ }
+
+ fn end_document(&mut self, _w: &mut dyn Write) -> std::io::Result<()> {
+ Ok(())
+ }
+
+ fn begin_api_list(&mut self, w: &mut dyn Write, title: &str) -> std::io::Result<()> {
+ self.in_list = true;
+ writeln!(w, "\n{} API Behavioral Requirements:", title)?;
+ writeln!(w)
+ }
+
+ fn api_item(&mut self, w: &mut dyn Write, name: &str, _api_type: &str) -> std::io::Result<()> {
+ writeln!(w, "- {} shall be available for {}", name, name.replace('_', " "))
+ }
+
+ fn end_api_list(&mut self, _w: &mut dyn Write) -> std::io::Result<()> {
+ self.in_list = false;
+ Ok(())
+ }
+
+ fn total_specs(&mut self, w: &mut dyn Write, count: usize) -> std::io::Result<()> {
+ writeln!(w, "\nTotal: {} kernel API specifications shall be enforced.", count)
+ }
+
+ fn begin_api_details(&mut self, w: &mut dyn Write, name: &str) -> std::io::Result<()> {
+ self.api_name = Some(name.to_string());
+ writeln!(w, "\nBehavioral Requirements for {}:", name)?;
+ writeln!(w)
+ }
+
+ fn end_api_details(&mut self, _w: &mut dyn Write) -> std::io::Result<()> {
+ self.api_name = None;
+ Ok(())
+ }
+
+ fn description(&mut self, w: &mut dyn Write, desc: &str) -> std::io::Result<()> {
+ if let Some(api_name) = &self.api_name {
+ writeln!(w, "- {} shall {}.", api_name, desc.trim_end_matches('.'))
+ } else {
+ writeln!(w, "- The API shall {}.", desc.trim_end_matches('.'))
+ }
+ }
+
+ fn long_description(&mut self, w: &mut dyn Write, desc: &str) -> std::io::Result<()> {
+ writeln!(w)?;
+ for line in desc.lines() {
+ if !line.trim().is_empty() {
+ writeln!(w, "{}", line)?;
+ }
+ }
+ writeln!(w)
+ }
+
+ fn begin_context_flags(&mut self, w: &mut dyn Write) -> std::io::Result<()> {
+ writeln!(w, "\nExecution Context Requirements:")?;
+ writeln!(w)
+ }
+
+ fn context_flag(&mut self, w: &mut dyn Write, flag: &str) -> std::io::Result<()> {
+ // Parse context flags and make them readable with specific requirements
+ match flag {
+ "Process context" => {
+ writeln!(w, "- The function shall be callable from process context.")?;
+ writeln!(w, " Process context allows the function to sleep, allocate memory with GFP_KERNEL, and access user space.")
+ }
+ "Softirq context" => {
+ writeln!(w, "- The function shall be callable from softirq context.")?;
+ writeln!(w, " In softirq context, the function shall not sleep and shall use GFP_ATOMIC for memory allocations.")
+ }
+ "Hardirq context" => {
+ writeln!(w, "- The function shall be callable from hardirq (interrupt) context.")?;
+ writeln!(w, " In hardirq context, the function shall not sleep, shall minimize execution time, and shall use GFP_ATOMIC for allocations.")
+ }
+ "NMI context" => {
+ writeln!(w, "- The function shall be callable from NMI (Non-Maskable Interrupt) context.")?;
+ writeln!(w, " In NMI context, the function shall not take any locks that might be held by interrupted code.")
+ }
+ "User mode" => {
+ writeln!(w, "- The function shall be callable when the CPU is in user mode.")?;
+ writeln!(w, " This typically applies to system call entry points.")
+ }
+ "Kernel mode" => {
+ writeln!(w, "- The function shall be callable when the CPU is in kernel mode.")
+ }
+ "May sleep" => {
+ writeln!(w, "- The function may sleep (block) during execution.")?;
+ writeln!(w, " Callers shall ensure they are in a context where sleeping is allowed (not in interrupt or atomic context).")
+ }
+ "Atomic context" => {
+ writeln!(w, "- The function shall be callable from atomic context.")?;
+ writeln!(w, " In atomic context, the function shall not sleep and shall complete quickly.")
+ }
+ "Preemptible" => {
+ writeln!(w, "- The function shall be callable when preemption is enabled.")?;
+ writeln!(w, " The function may be preempted by higher priority tasks.")
+ }
+ "Migration disabled" => {
+ writeln!(w, "- The function shall be callable when CPU migration is disabled.")?;
+ writeln!(w, " The function shall not rely on being able to migrate between CPUs.")
+ }
+ _ => {
+ // Fallback for unrecognized flags
+ writeln!(w, "- The function shall be callable from {} context.", flag)
+ }
+ }
+ }
+
+ fn end_context_flags(&mut self, _w: &mut dyn Write) -> std::io::Result<()> {
+ Ok(())
+ }
+
+ fn begin_parameters(&mut self, w: &mut dyn Write, _count: u32) -> std::io::Result<()> {
+ writeln!(w, "\nParameter Requirements:")
+ }
+
+ fn parameter(&mut self, w: &mut dyn Write, param: &ParamSpec) -> std::io::Result<()> {
+ writeln!(w)?;
+ writeln!(w, "- If {} is provided, it shall be {}.",
+ param.name, param.description.trim_end_matches('.'))?;
+
+ // Only show meaningful numeric constraints
+ if let Some(min) = param.min_value {
+ if let Some(max) = param.max_value {
+ if min != 0 || max != 0 {
+ writeln!(w, "\n- If {} is less than {} or greater than {}, the operation shall fail.",
+ param.name, min, max)?;
+ }
+ } else if min != 0 {
+ writeln!(w, "\n- If {} is less than {}, the operation shall fail.",
+ param.name, min)?;
+ }
+ } else if let Some(max) = param.max_value {
+ if max != 0 {
+ writeln!(w, "\n- If {} is greater than {}, the operation shall fail.",
+ param.name, max)?;
+ }
+ }
+
+ if let Some(constraint) = ¶m.constraint {
+ if !constraint.is_empty() {
+ let constraint_text = constraint.trim_end_matches('.');
+ // Handle constraints that start with "Must be" or similar
+ if constraint_text.to_lowercase().starts_with("must be ") {
+ let requirement = &constraint_text[8..]; // Skip "Must be "
+ writeln!(w, "\n- If {} is not {}, the operation shall fail.",
+ param.name, requirement)?;
+ } else if constraint_text.to_lowercase().starts_with("must ") {
+ let requirement = &constraint_text[5..]; // Skip "Must "
+ writeln!(w, "\n- If {} does not {}, the operation shall fail.",
+ param.name, requirement)?;
+ } else if constraint_text.contains(" must ") || constraint_text.contains(" should ") {
+ // Reformat constraints with must/should in the middle
+ writeln!(w, "\n- {} shall satisfy: {}.",
+ param.name, constraint_text)?;
+ } else {
+ // Default format for other constraints
+ writeln!(w, "\n- If {} is not {}, the operation shall fail.",
+ param.name, constraint_text)?;
+ }
+ }
+ }
+
+ // Only show valid_mask if it's not 0
+ if let Some(mask) = param.valid_mask {
+ if mask != 0 {
+ writeln!(w, "\n- If {} contains bits not set in 0x{:x}, the operation shall fail.",
+ param.name, mask)?;
+ }
+ }
+
+ Ok(())
+ }
+
+ fn end_parameters(&mut self, _w: &mut dyn Write) -> std::io::Result<()> {
+ Ok(())
+ }
+
+ fn return_spec(&mut self, w: &mut dyn Write, ret: &ReturnSpec) -> std::io::Result<()> {
+ writeln!(w, "\nReturn Value Behavior:")?;
+ writeln!(w)?;
+
+ if let Some(success) = ret.success_value {
+ writeln!(w, "- If the operation succeeds, the function shall return {}.", success)?;
+ } else if let Some(min) = ret.success_min {
+ if let Some(max) = ret.success_max {
+ writeln!(w, "- If the operation succeeds, the function shall return a value between {} and {} inclusive.", min, max)?;
+ } else {
+ writeln!(w, "- If the operation succeeds, the function shall return a value greater than or equal to {}.", min)?;
+ }
+ }
+
+ if !ret.error_values.is_empty() {
+ writeln!(w, "\n- If the operation fails, the function shall return one of the specified negative error values.")?;
+ }
+
+ Ok(())
+ }
+
+ fn begin_errors(&mut self, w: &mut dyn Write, _count: u32) -> std::io::Result<()> {
+ writeln!(w, "\nError Handling:")?;
+ Ok(())
+ }
+
+ fn error(&mut self, w: &mut dyn Write, error: &ErrorSpec) -> std::io::Result<()> {
+ writeln!(w)?;
+ let condition = if error.condition.is_empty() {
+ error.description.to_lowercase().trim_end_matches('.').to_string()
+ } else {
+ error.condition.to_lowercase()
+ };
+ writeln!(w, "- If {condition}, the function shall return -{}.", error.name)?;
+
+ // Add description if available and different from condition
+ if !error.description.is_empty() && error.description != error.condition {
+ writeln!(w, " {}", error.description)?;
+ }
+
+ Ok(())
+ }
+
+ fn end_errors(&mut self, _w: &mut dyn Write) -> std::io::Result<()> {
+ Ok(())
+ }
+
+ fn examples(&mut self, w: &mut dyn Write, examples: &str) -> std::io::Result<()> {
+ writeln!(w, "\nExample Usage:")?;
+ writeln!(w)?;
+ writeln!(w, "```")?;
+ write!(w, "{}", examples)?;
+ writeln!(w, "```")
+ }
+
+ fn notes(&mut self, w: &mut dyn Write, notes: &str) -> std::io::Result<()> {
+ writeln!(w, "\nImplementation Notes:")?;
+ writeln!(w)?;
+
+ // Split notes into sentences and format each as a behavioral requirement
+ let sentences: Vec<&str> = notes.split(". ")
+ .filter(|s| !s.trim().is_empty())
+ .collect();
+
+ for sentence in sentences {
+ let trimmed = sentence.trim().trim_end_matches('.');
+ if trimmed.is_empty() {
+ continue;
+ }
+
+ // Check if it already contains "shall" or similar
+ if trimmed.contains("shall") || trimmed.contains("must") {
+ writeln!(w, "- {}.", trimmed)?;
+ } else if trimmed.starts_with("On ") || trimmed.starts_with("If ") || trimmed.starts_with("When ") {
+ // These are already conditional, just add shall
+ writeln!(w, "- {}, the behavior shall be as described.", trimmed)?;
+ } else {
+ // Convert to a shall statement
+ writeln!(w, "- The implementation shall ensure that {}.",
+ trimmed.chars().next().unwrap().to_lowercase().collect::<String>() + &trimmed[1..])?;
+ }
+ }
+ Ok(())
+ }
+
+ fn since_version(&mut self, w: &mut dyn Write, version: &str) -> std::io::Result<()> {
+ writeln!(w, "\n- If kernel version is {} or later, this API shall be available.", version)
+ }
+
+ fn sysfs_subsystem(&mut self, w: &mut dyn Write, subsystem: &str) -> std::io::Result<()> {
+ writeln!(w, "- If accessed through sysfs, the attribute shall be located in the {} subsystem.", subsystem)
+ }
+
+ fn sysfs_path(&mut self, w: &mut dyn Write, path: &str) -> std::io::Result<()> {
+ writeln!(w, "\n- If the sysfs interface is mounted, the attribute shall be accessible at {}.", path)
+ }
+
+ fn sysfs_permissions(&mut self, w: &mut dyn Write, perms: &str) -> std::io::Result<()> {
+ writeln!(w, "\n- If the attribute exists, its permissions shall be set to {}.", perms)
+ }
+
+ fn socket_state(&mut self, w: &mut dyn Write, state: &SocketStateSpec) -> std::io::Result<()> {
+ writeln!(w, "\nSocket State Behavior:")?;
+ writeln!(w)?;
+
+ if !state.required_states.is_empty() {
+ let states_str = state.required_states.join(" or ");
+ writeln!(w, "- If the socket is not in {} state, the operation shall fail.", states_str)?;
+ }
+
+ if !state.forbidden_states.is_empty() {
+ for s in &state.forbidden_states {
+ writeln!(w, "\n- If the socket is in {} state, the operation shall fail.", s)?;
+ }
+ }
+
+ if let Some(result) = &state.resulting_state {
+ writeln!(w, "\n- If the operation succeeds, the socket state shall transition to {}.", result)?;
+ }
+
+ Ok(())
+ }
+
+ fn begin_protocol_behaviors(&mut self, w: &mut dyn Write) -> std::io::Result<()> {
+ writeln!(w, "\nProtocol-Specific Behavior:")
+ }
+
+ fn protocol_behavior(&mut self, w: &mut dyn Write, behavior: &ProtocolBehaviorSpec) -> std::io::Result<()> {
+ writeln!(w)?;
+ writeln!(w, "- If protocol is {}, {}.",
+ behavior.applicable_protocols, behavior.behavior)?;
+
+ if let Some(flags) = &behavior.protocol_flags {
+ writeln!(w, "\n- If protocol is {} and flags {} are set, the behavior shall be modified accordingly.",
+ behavior.applicable_protocols, flags)?;
+ }
+
+ Ok(())
+ }
+
+ fn end_protocol_behaviors(&mut self, _w: &mut dyn Write) -> std::io::Result<()> {
+ Ok(())
+ }
+
+ fn begin_addr_families(&mut self, w: &mut dyn Write) -> std::io::Result<()> {
+ writeln!(w, "\nAddress Family Behavior:")
+ }
+
+ fn addr_family(&mut self, w: &mut dyn Write, family: &AddrFamilySpec) -> std::io::Result<()> {
+ writeln!(w)?;
+ writeln!(w, "- If address family is {} ({}), the address structure size shall be {} bytes.",
+ family.family, family.family_name, family.addr_struct_size)?;
+
+ writeln!(w, "\n- If address family is {} and address length is less than {} or greater than {}, the operation shall fail.",
+ family.family, family.min_addr_len, family.max_addr_len)?;
+
+ Ok(())
+ }
+
+ fn end_addr_families(&mut self, _w: &mut dyn Write) -> std::io::Result<()> {
+ Ok(())
+ }
+
+ fn buffer_spec(&mut self, w: &mut dyn Write, spec: &BufferSpec) -> std::io::Result<()> {
+ writeln!(w, "\nBuffer Behavior:")?;
+ writeln!(w)?;
+
+ if let Some(min) = spec.min_buffer_size {
+ writeln!(w, "- If the buffer size is less than {} bytes, the operation shall fail.", min)?;
+ }
+
+ if let Some(max) = spec.max_buffer_size {
+ writeln!(w, "\n- If the buffer size exceeds {} bytes, the excess data shall be truncated.", max)?;
+ }
+
+ if let Some(behaviors) = &spec.buffer_behaviors {
+ writeln!(w, "\n- When handling buffers, the following behavior shall apply: {}.", behaviors)?;
+ }
+
+ Ok(())
+ }
+
+ fn async_spec(&mut self, w: &mut dyn Write, spec: &AsyncSpec) -> std::io::Result<()> {
+ writeln!(w, "\nAsynchronous Behavior:")?;
+ writeln!(w)?;
+
+ if let Some(_modes) = &spec.supported_modes {
+ writeln!(w, "- If O_NONBLOCK is set and the operation would block, the function shall return -EAGAIN or -EWOULDBLOCK.")?;
+ }
+
+ if let Some(errno) = spec.nonblock_errno {
+ writeln!(w, "\n- If the file descriptor is in non-blocking mode and no data is available, the function shall return -{}.", errno)?;
+ }
+
+ Ok(())
+ }
+
+ fn net_data_transfer(&mut self, w: &mut dyn Write, desc: &str) -> std::io::Result<()> {
+ writeln!(w, "\nData Transfer Behavior:")?;
+ writeln!(w)?;
+ writeln!(w, "- When transferring data, the operation shall {}.", desc.trim_end_matches('.'))
+ }
+
+ fn begin_capabilities(&mut self, w: &mut dyn Write) -> std::io::Result<()> {
+ writeln!(w, "\nCapability Requirements:")
+ }
+
+ fn capability(&mut self, w: &mut dyn Write, cap: &CapabilitySpec) -> std::io::Result<()> {
+ writeln!(w)?;
+ writeln!(w, "- If the process attempts to {}, {} capability shall be checked.",
+ cap.action, cap.name)?;
+ writeln!(w)?;
+ writeln!(w, "- If {} is present, {}.", cap.name, cap.allows)?;
+ writeln!(w)?;
+ writeln!(w, "- If {} is not present, {}.", cap.name, cap.without_cap)?;
+
+ Ok(())
+ }
+
+ fn end_capabilities(&mut self, _w: &mut dyn Write) -> std::io::Result<()> {
+ Ok(())
+ }
+
+ fn begin_signals(&mut self, w: &mut dyn Write, _count: u32) -> std::io::Result<()> {
+ writeln!(w, "\nSignal Behavior:")?;
+ Ok(())
+ }
+
+ fn signal(&mut self, w: &mut dyn Write, signal: &SignalSpec) -> std::io::Result<()> {
+ writeln!(w)?;
+
+ // Skip signals with no meaningful description
+ if let Some(desc) = &signal.description {
+ if !desc.is_empty() {
+ writeln!(w, "- {}: {}.", signal.signal_name, desc)?;
+ return Ok(());
+ }
+ }
+
+ // Default behavior based on direction
+ if signal.direction == 1 { // Sends
+ writeln!(w, "- If the conditions for {} are met, the signal shall be sent to the target process.",
+ signal.signal_name)?;
+ } else if signal.direction == 2 { // Receives
+ writeln!(w, "- If {} is received and not blocked, the operation shall be interrupted.",
+ signal.signal_name)?;
+
+ if signal.restartable {
+ writeln!(w, "\n- If {} is received and SA_RESTART is set, the operation shall be automatically restarted.",
+ signal.signal_name)?;
+ }
+ } else {
+ // Direction 0 or other - just note the signal handling
+ writeln!(w, "- {} shall be handled according to its default behavior.", signal.signal_name)?;
+ }
+
+ if let Some(errno) = signal.error_on_signal {
+ if errno != 0 {
+ writeln!(w, "\n- If interrupted by {}, the function shall return -{}.",
+ signal.signal_name, errno)?;
+ }
+ }
+
+ Ok(())
+ }
+
+ fn end_signals(&mut self, _w: &mut dyn Write) -> std::io::Result<()> {
+ Ok(())
+ }
+
+ fn begin_signal_masks(&mut self, w: &mut dyn Write, count: u32) -> std::io::Result<()> {
+ writeln!(w, "\n### Signal Mask Requirements")?;
+ if count > 0 {
+ writeln!(w, "The API SHALL support the following signal mask operations:")?;
+ }
+ Ok(())
+ }
+
+ fn signal_mask(&mut self, w: &mut dyn Write, mask: &SignalMaskSpec) -> std::io::Result<()> {
+ writeln!(w, "\n- **{}**: {}", mask.name, mask.description)?;
+ Ok(())
+ }
+
+ fn end_signal_masks(&mut self, _w: &mut dyn Write) -> std::io::Result<()> {
+ Ok(())
+ }
+
+ fn begin_side_effects(&mut self, w: &mut dyn Write, _count: u32) -> std::io::Result<()> {
+ writeln!(w, "\nSide Effects:")?;
+ Ok(())
+ }
+
+ fn side_effect(&mut self, w: &mut dyn Write, effect: &SideEffectSpec) -> std::io::Result<()> {
+ writeln!(w)?;
+ if let Some(condition) = &effect.condition {
+ writeln!(w, "- If {}, {} shall be {}.",
+ condition, effect.target, effect.description.trim_end_matches('.'))?;
+ } else {
+ writeln!(w, "- When the operation executes, {} shall be {}.",
+ effect.target, effect.description.trim_end_matches('.'))?;
+ }
+
+ if effect.reversible {
+ writeln!(w, "\n- If the operation is rolled back, the effect on {} shall be reversed.",
+ effect.target)?;
+ }
+
+ Ok(())
+ }
+
+ fn end_side_effects(&mut self, _w: &mut dyn Write) -> std::io::Result<()> {
+ Ok(())
+ }
+
+ fn begin_state_transitions(&mut self, w: &mut dyn Write, _count: u32) -> std::io::Result<()> {
+ writeln!(w, "\nState Transitions:")?;
+ Ok(())
+ }
+
+ fn state_transition(&mut self, w: &mut dyn Write, trans: &StateTransitionSpec) -> std::io::Result<()> {
+ writeln!(w)?;
+ if let Some(condition) = &trans.condition {
+ writeln!(w, "- If {} is in {} state and {}, it shall transition to {} state.",
+ trans.object, trans.from_state, condition, trans.to_state)?;
+ } else {
+ writeln!(w, "- If {} is in {} state, it shall transition to {} state.",
+ trans.object, trans.from_state, trans.to_state)?;
+ }
+
+ Ok(())
+ }
+
+ fn end_state_transitions(&mut self, _w: &mut dyn Write) -> std::io::Result<()> {
+ Ok(())
+ }
+
+ fn begin_constraints(&mut self, w: &mut dyn Write, _count: u32) -> std::io::Result<()> {
+ writeln!(w, "\nConstraints:")?;
+ Ok(())
+ }
+
+ fn constraint(&mut self, w: &mut dyn Write, constraint: &ConstraintSpec) -> std::io::Result<()> {
+ writeln!(w)?;
+ if let Some(expr) = &constraint.expression {
+ if expr.is_empty() {
+ writeln!(w, "- {}: {}.", constraint.name, constraint.description)?;
+ } else {
+ writeln!(w, "- If {} is violated, the operation shall fail.", constraint.name)?;
+ writeln!(w, " Constraint: {}", expr)?;
+ }
+ } else {
+ writeln!(w, "- {}: {}.", constraint.name, constraint.description)?;
+ }
+
+ Ok(())
+ }
+
+ fn end_constraints(&mut self, _w: &mut dyn Write) -> std::io::Result<()> {
+ Ok(())
+ }
+
+ fn begin_locks(&mut self, w: &mut dyn Write, _count: u32) -> std::io::Result<()> {
+ writeln!(w, "\nLocking Behavior:")?;
+ Ok(())
+ }
+
+ fn lock(&mut self, w: &mut dyn Write, lock: &LockSpec) -> std::io::Result<()> {
+ writeln!(w)?;
+
+ // Always show lock information if we have a description
+ if !lock.description.is_empty() {
+ let lock_type_str = match lock.lock_type {
+ 1 => "mutex",
+ 2 => "spinlock",
+ 3 => "rwlock",
+ 4 => "semaphore",
+ 5 => "RCU",
+ _ => "lock",
+ };
+ writeln!(w, "- The {} {} shall be used for: {}",
+ lock.lock_name, lock_type_str, lock.description)?;
+ }
+
+ if lock.held_on_entry {
+ writeln!(w, "- If {} is not held on entry, the operation shall fail.", lock.lock_name)?;
+ }
+
+ if lock.acquired && !lock.held_on_entry {
+ writeln!(w, "- Before accessing the protected resource, {} shall be acquired.", lock.lock_name)?;
+ }
+
+ if lock.released && lock.held_on_exit {
+ writeln!(w, "- If the operation succeeds and no error path is taken, {} shall remain held on exit.", lock.lock_name)?;
+ } else if lock.released {
+ writeln!(w, "- Before returning, {} shall be released.", lock.lock_name)?;
+ }
+
+ Ok(())
+ }
+
+ fn end_locks(&mut self, _w: &mut dyn Write) -> std::io::Result<()> {
+ Ok(())
+ }
+}
\ No newline at end of file
diff --git a/tools/kapi/src/main.rs b/tools/kapi/src/main.rs
new file mode 100644
index 0000000000000..76416a9364010
--- /dev/null
+++ b/tools/kapi/src/main.rs
@@ -0,0 +1,130 @@
+//! kapi - Kernel API Specification Tool
+//!
+//! This tool extracts and displays kernel API specifications from multiple sources:
+//! - Kernel source code (KAPI macros)
+//! - Compiled vmlinux binaries (`.kapi_specs` ELF section)
+//! - Running kernel via debugfs
+
+use anyhow::Result;
+use clap::Parser;
+use std::io::{self, Write};
+
+mod formatter;
+mod extractor;
+
+use formatter::{OutputFormat, create_formatter};
+use extractor::{ApiExtractor, VmlinuxExtractor, SourceExtractor, DebugfsExtractor};
+
+#[derive(Parser, Debug)]
+#[command(author, version, about, long_about = None)]
+struct Args {
+ /// Path to the vmlinux file
+ #[arg(long, value_name = "PATH", group = "input")]
+ vmlinux: Option<String>,
+
+ /// Path to kernel source directory or file
+ #[arg(long, value_name = "PATH", group = "input")]
+ source: Option<String>,
+
+ /// Path to debugfs (defaults to /sys/kernel/debug if not specified)
+ #[arg(long, value_name = "PATH", group = "input")]
+ debugfs: Option<String>,
+
+ /// Optional: Name of specific API to show details for
+ api_name: Option<String>,
+
+ /// Output format
+ #[arg(long, short = 'f', default_value = "plain")]
+ format: String,
+}
+
+fn main() -> Result<()> {
+ let args = Args::parse();
+
+ let output_format: OutputFormat = args.format.parse()
+ .map_err(|e: String| anyhow::anyhow!(e))?;
+
+ let extractor: Box<dyn ApiExtractor> = match (args.vmlinux, args.source, args.debugfs.clone()) {
+ (Some(vmlinux_path), None, None) => {
+ Box::new(VmlinuxExtractor::new(&vmlinux_path)?)
+ }
+ (None, Some(source_path), None) => {
+ Box::new(SourceExtractor::new(&source_path)?)
+ }
+ (None, None, Some(_) | None) => {
+ // If debugfs is specified or no input is provided, use debugfs
+ Box::new(DebugfsExtractor::new(args.debugfs)?)
+ }
+ _ => {
+ anyhow::bail!("Please specify only one of --vmlinux, --source, or --debugfs")
+ }
+ };
+
+ display_apis(extractor.as_ref(), args.api_name, output_format)
+}
+
+fn display_apis(extractor: &dyn ApiExtractor, api_name: Option<String>, output_format: OutputFormat) -> Result<()> {
+ let mut formatter = create_formatter(output_format);
+ let mut stdout = io::stdout();
+
+ formatter.begin_document(&mut stdout)?;
+
+ if let Some(api_name_req) = api_name {
+ // Use the extractor to display API details
+ if let Some(_spec) = extractor.extract_by_name(&api_name_req)? {
+ extractor.display_api_details(&api_name_req, &mut *formatter, &mut stdout)?;
+ } else if output_format == OutputFormat::Plain {
+ writeln!(stdout, "\nAPI '{}' not found.", api_name_req)?;
+ writeln!(stdout, "\nAvailable APIs:")?;
+ for spec in extractor.extract_all()? {
+ writeln!(stdout, " {} ({})", spec.name, spec.api_type)?;
+ }
+ }
+ } else {
+ // Display list of APIs using the extractor
+ let all_specs = extractor.extract_all()?;
+ let syscalls: Vec<_> = all_specs.iter().filter(|s| s.api_type == "syscall").collect();
+ let ioctls: Vec<_> = all_specs.iter().filter(|s| s.api_type == "ioctl").collect();
+ let functions: Vec<_> = all_specs.iter().filter(|s| s.api_type == "function").collect();
+ let sysfs: Vec<_> = all_specs.iter().filter(|s| s.api_type == "sysfs").collect();
+
+ if !syscalls.is_empty() {
+ formatter.begin_api_list(&mut stdout, "System Calls")?;
+ for spec in syscalls {
+ formatter.api_item(&mut stdout, &spec.name, &spec.api_type)?;
+ }
+ formatter.end_api_list(&mut stdout)?;
+ }
+
+ if !ioctls.is_empty() {
+ formatter.begin_api_list(&mut stdout, "IOCTLs")?;
+ for spec in ioctls {
+ formatter.api_item(&mut stdout, &spec.name, &spec.api_type)?;
+ }
+ formatter.end_api_list(&mut stdout)?;
+ }
+
+ if !functions.is_empty() {
+ formatter.begin_api_list(&mut stdout, "Functions")?;
+ for spec in functions {
+ formatter.api_item(&mut stdout, &spec.name, &spec.api_type)?;
+ }
+ formatter.end_api_list(&mut stdout)?;
+ }
+
+ if !sysfs.is_empty() {
+ formatter.begin_api_list(&mut stdout, "Sysfs Attributes")?;
+ for spec in sysfs {
+ formatter.api_item(&mut stdout, &spec.name, &spec.api_type)?;
+ }
+ formatter.end_api_list(&mut stdout)?;
+ }
+
+ formatter.total_specs(&mut stdout, all_specs.len())?;
+ }
+
+ formatter.end_document(&mut stdout)?;
+
+ Ok(())
+}
+
--
2.39.5
Powered by blists - more mailing lists