From nobody Fri Oct 3 21:07:27 2025 Received: from smtp.kernel.org (aws-us-west-2-korg-mail-1.web.codeaurora.org [10.30.226.201]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 49781311C37; Mon, 25 Aug 2025 18:14:43 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=10.30.226.201 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1756145683; cv=none; b=ECR+p38MeUBIQruJaJMvQTYTOdJcTadb6D3WDNMcG6tJIgr0lx3bZ9JP9JD6AZRuHvSnLWKheymVh5dzOlS553W+cB5BFW2S9KNfKztP0zNVQiIxvVWGzgv3I6iIOqbNo+7VAHR1t4yN/LbpQ9lbeLWXWQU+gcCDWpVH+CvFMOs= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1756145683; c=relaxed/simple; bh=eYLBbwKzBHEdhKyawfY9FzVAzFj8dgzKiWp6zOOcbxM=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version:Content-Type; b=PGOmX8ywqpWQmia/RL3FMAHwFgIX1S+pXZs+tG63XsxGqXGWRj1H/qYCnSoaGZjLl1WH2ASXIcQxWqMk5B+QTq3nnRmVLT3jmh5N9W7qmreusaF4ATxr1vepQ8nKQw37aI1nFILEiJMVvE5ZG0Znb7HVIrXheEPoMJ4PKEkgpBw= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b=Fq0EAC6S; arc=none smtp.client-ip=10.30.226.201 Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b="Fq0EAC6S" Received: by smtp.kernel.org (Postfix) with ESMTPSA id 82E3CC16AAE; Mon, 25 Aug 2025 18:14:42 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=kernel.org; s=k20201202; t=1756145683; bh=eYLBbwKzBHEdhKyawfY9FzVAzFj8dgzKiWp6zOOcbxM=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=Fq0EAC6S7w0V7aE0dBHQAJpZXi8DDGprovvxSa/w7ID1aXtVihtBpnaemdihZhnHb g/q8S5TXE5mLRGP/Qiww5yOlhj3Hf/+arIB327ZrnMExtHGmACiEVVlRAZxJ0rPCED WmhAY1oo9Hhsoy8AGxJ0avK+hNpSr8QTGPV+1/nuDL33oQoHethez7Zam7ksQCXIT/ F7Iewqy85M4LFSl9/Ldp9uSPty2CcTqAgjwXmGSDg0qwsr6gDQnGD97YXjj9uAJCtc wp0eq87GSvYlMG/3hPnSwoNnaelP0CMEauRVzRVJ6ifIgBCNIEATXxtZvItywqEbTK D1AwLQ3dNXkSQ== From: Sasha Levin To: linux-api@vger.kernel.org, linux-doc@vger.kernel.org, linux-kernel@vger.kernel.org, tools@kernel.org Cc: Sasha Levin Subject: [RFC PATCH v4 7/7] tools/kapi: Add kernel API specification extraction tool Date: Mon, 25 Aug 2025 14:14:34 -0400 Message-ID: <20250825181434.3340805-8-sashal@kernel.org> X-Mailer: git-send-email 2.50.1 In-Reply-To: <20250825181434.3340805-1-sashal@kernel.org> References: <20250825181434.3340805-1-sashal@kernel.org> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: quoted-printable The kapi tool extracts and displays kernel API specifications. Signed-off-by: Sasha Levin --- Documentation/admin-guide/kernel-api-spec.rst | 198 +++- tools/kapi/.gitignore | 4 + tools/kapi/Cargo.toml | 19 + tools/kapi/src/extractor/debugfs.rs | 442 +++++++++ tools/kapi/src/extractor/kerneldoc_parser.rs | 694 ++++++++++++++ tools/kapi/src/extractor/mod.rs | 461 +++++++++ tools/kapi/src/extractor/source_parser.rs | 213 +++++ .../src/extractor/vmlinux/binary_utils.rs | 180 ++++ .../src/extractor/vmlinux/magic_finder.rs | 102 ++ tools/kapi/src/extractor/vmlinux/mod.rs | 869 +++++++++++++++++ tools/kapi/src/formatter/json.rs | 468 +++++++++ tools/kapi/src/formatter/mod.rs | 145 +++ tools/kapi/src/formatter/plain.rs | 558 +++++++++++ tools/kapi/src/formatter/rst.rs | 621 ++++++++++++ tools/kapi/src/formatter/shall.rs | 891 ++++++++++++++++++ tools/kapi/src/main.rs | 116 +++ 16 files changed, 5978 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/kerneldoc_parser.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/magic_finder.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 3a63f6711e27..9b452753111a 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. =20 3. **Support Tooling**: Export API specifications in machine-readable form= ats for - use by static analyzers, documentation generators, and development tool= s. + use by static analyzers, documentation generators, and development tool= s. The + ``kapi`` tool (see `The kapi Tool`_) provides comprehensive extraction = and + formatting capabilities. =20 4. **Enhance Debugging**: Provide detailed API information at runtime thro= ugh debugfs for debugging and introspection. @@ -71,6 +73,13 @@ The framework consists of several key components: - Type-safe parameter specifications - Context and constraint definitions =20 +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 ---------- =20 @@ -344,8 +353,177 @@ Documentation Generation ------------------------ =20 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 +=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D + +Overview +-------- + +The ``kapi`` tool is a userspace utility that extracts and displays kernel= API +specifications from multiple sources. It provides a unified interface to a= ccess +API documentation whether from compiled kernels, source code, or runtime s= ystems. + +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 ``: Extract from compiled kernel binary +- ``--source ``: Extract from kernel source code +- ``--debugfs ``: Extract from debugfs (default: /sys/kernel/debug) +- ``-f, --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: + =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D + Description: Read from a file descriptor + + Detailed Description: + Reads up to count bytes from file descriptor fd into the buffer starti= ng 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 + =3D=3D=3D=3D=3D=3D=3D=3D + + **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[] | contain= s("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 express= ions +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``) =20 IDE Integration --------------- @@ -357,6 +535,11 @@ Modern IDEs can use the JSON export for: - Context validation - Error code documentation =20 +Example IDE integration:: + + # Generate IDE completion data + $ kapi --format json > .vscode/kernel-apis.json + Testing Framework ----------------- =20 @@ -367,6 +550,15 @@ The framework includes test helpers:: kapi_test_api("kmalloc", test_cases); #endif =20 +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 =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D =20 diff --git a/tools/kapi/.gitignore b/tools/kapi/.gitignore new file mode 100644 index 000000000000..1390bfc12686 --- /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 000000000000..4e6bcb10d132 --- /dev/null +++ b/tools/kapi/Cargo.toml @@ -0,0 +1,19 @@ +[package] +name =3D "kapi" +version =3D "0.1.0" +edition =3D "2024" +authors =3D ["Sasha Levin "] +description =3D "Tool for extracting and displaying kernel API specificati= ons" +license =3D "GPL-2.0" + +[dependencies] +goblin =3D "0.10" +clap =3D { version =3D "4.4", features =3D ["derive"] } +anyhow =3D "1.0" +serde =3D { version =3D "1.0", features =3D ["derive"] } +serde_json =3D "1.0" +regex =3D "1.10" +walkdir =3D "2.4" + +[dev-dependencies] +tempfile =3D "3.8" diff --git a/tools/kapi/src/extractor/debugfs.rs b/tools/kapi/src/extractor= /debugfs.rs new file mode 100644 index 000000000000..698c51e50438 --- /dev/null +++ b/tools/kapi/src/extractor/debugfs.rs @@ -0,0 +1,442 @@ +use crate::formatter::OutputFormatter; +use anyhow::{Context, Result, bail}; +use serde::Deserialize; +use std::fs; +use std::io::Write; +use std::path::PathBuf; + +use super::{ApiExtractor, ApiSpec, CapabilitySpec, display_api_spec}; + +#[derive(Deserialize)] +struct KernelApiJson { + name: String, + api_type: Option, + version: Option, + description: Option, + long_description: Option, + context_flags: Option, + since_version: Option, + examples: Option, + notes: Option, + capabilities: Option>, +} + +#[derive(Deserialize)] +struct KernelCapabilityJson { + capability: i32, + name: String, + action: String, + allows: String, + without_cap: String, + check_condition: Option, + priority: Option, + alternatives: Option>, +} + +/// 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) -> Result { + let path =3D match debugfs_path { + Some(p) =3D> PathBuf::from(p), + None =3D> 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 =3D 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> { + let list_path =3D self.debugfs_path.join("kapi/list"); + let content =3D fs::read_to_string(&list_path) + .with_context(|| format!("Failed to read {}", list_path.displa= y()))?; + + let mut apis =3D Vec::new(); + let mut in_list =3D false; + + for line in content.lines() { + if line.contains("=3D=3D=3D") { + in_list =3D 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) =3D line.split(" - ").next() { + apis.push(name.trim().to_string()); + } + } + } + + Ok(apis) + } + + /// Try to parse JSON content, convert context flags from u32 to strin= g representations + fn parse_context_flags(flags: u32) -> Vec { + let mut result =3D Vec::new(); + + // These values should match KAPI_CTX_* flags from kernel + if flags & (1 << 0) !=3D 0 { + result.push("PROCESS".to_string()); + } + if flags & (1 << 1) !=3D 0 { + result.push("SOFTIRQ".to_string()); + } + if flags & (1 << 2) !=3D 0 { + result.push("HARDIRQ".to_string()); + } + if flags & (1 << 3) !=3D 0 { + result.push("NMI".to_string()); + } + if flags & (1 << 4) !=3D 0 { + result.push("ATOMIC".to_string()); + } + if flags & (1 << 5) !=3D 0 { + result.push("SLEEPABLE".to_string()); + } + if flags & (1 << 6) !=3D 0 { + result.push("PREEMPT_DISABLED".to_string()); + } + if flags & (1 << 7) !=3D 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" =3D> "Bypasses check".to_string(), + "increase_limit" =3D> "Increases limit".to_string(), + "override_restriction" =3D> "Overrides restriction".to_string(= ), + "grant_permission" =3D> "Grants permission".to_string(), + "modify_behavior" =3D> "Modifies behavior".to_string(), + "access_resource" =3D> "Allows resource access".to_string(), + "perform_operation" =3D> "Allows operation".to_string(), + _ =3D> action.to_string(), + } + } + + /// Try to parse as JSON first + fn try_parse_json(&self, content: &str) -> Option { + let json_data: KernelApiJson =3D serde_json::from_str(content).ok(= )?; + + let mut spec =3D ApiSpec { + name: json_data.name, + api_type: json_data.api_type.unwrap_or_else(|| "unknown".to_st= ring()), + 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![], + struct_specs: vec![], + }; + + // Convert capabilities + if let Some(caps) =3D 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 { + let spec_path =3D self.debugfs_path.join(format!("kapi/specs/{}", = api_name)); + let content =3D fs::read_to_string(&spec_path) + .with_context(|| format!("Failed to read {}", spec_path.displa= y()))?; + + // Try JSON parsing first + if let Some(spec) =3D self.try_parse_json(&content) { + return Ok(spec); + } + + // Fall back to plain text parsing + let mut spec =3D 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![], + struct_specs: vec![], + }; + + // Parse the content + let mut collecting_multiline =3D false; + let mut multiline_buffer =3D String::new(); + let mut multiline_field =3D ""; + let mut parsing_capability =3D false; + let mut current_capability: Option =3D 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) =3D current_capability.take() { + spec.capabilities.push(cap); + } + + let parts: Vec<&str> =3D line.trim().split(" (").collect(); + if parts.len() =3D=3D 2 { + let cap_name =3D parts[0].to_string(); + let cap_id =3D parts[1].trim_end_matches("):").parse()= .unwrap_or(0); + current_capability =3D 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 =3D true; + } + continue; + } + if parsing_capability && line.starts_with(" ") { + // Parse capability fields + if let Some(ref mut cap) =3D current_capability { + if let Some(action) =3D line.strip_prefix(" Action:= ") { + cap.action =3D action.to_string(); + } else if let Some(allows) =3D line.strip_prefix(" = Allows: ") { + cap.allows =3D allows.to_string(); + } else if let Some(without) =3D line.strip_prefix(" = Without: ") { + cap.without_cap =3D without.to_string(); + } else if let Some(cond) =3D line.strip_prefix(" Co= ndition: ") { + cap.check_condition =3D Some(cond.to_string()); + } else if let Some(prio) =3D line.strip_prefix(" Pr= iority: ") { + cap.priority =3D prio.parse().ok(); + } else if let Some(alts) =3D line.strip_prefix(" Al= ternatives: ") { + cap.alternatives =3D + alts.split(", ").filter_map(|s| s.parse().ok()= ).collect(); + } + } + continue; + } + if parsing_capability && !line.starts_with(" ") { + // End of capabilities section + if let Some(cap) =3D current_capability.take() { + spec.capabilities.push(cap); + } + parsing_capability =3D false; + } + + // Handle section headers + if line.starts_with("Parameters (") { + if let Some(count_str) =3D line + .strip_prefix("Parameters (") + .and_then(|s| s.strip_suffix("):")) + { + spec.param_count =3D count_str.parse().ok(); + } + continue; + } else if line.starts_with("Errors (") { + if let Some(count_str) =3D line + .strip_prefix("Errors (") + .and_then(|s| s.strip_suffix("):")) + { + spec.error_count =3D count_str.parse().ok(); + } + continue; + } else if line.starts_with("Examples:") { + collecting_multiline =3D true; + multiline_field =3D "examples"; + multiline_buffer.clear(); + continue; + } else if line.starts_with("Notes:") { + collecting_multiline =3D true; + multiline_field =3D "notes"; + multiline_buffer.clear(); + continue; + } + + // Handle multiline sections + if collecting_multiline { + if line.trim().is_empty() && multiline_buffer.ends_with("\= n\n") { + collecting_multiline =3D false; + match multiline_field { + "examples" =3D> spec.examples =3D Some(multiline_b= uffer.trim().to_string()), + "notes" =3D> spec.notes =3D Some(multiline_buffer.= trim().to_string()), + _ =3D> {} + } + 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) =3D line.strip_prefix("Description: ") { + spec.description =3D Some(desc.to_string()); + } else if let Some(long_desc) =3D line.strip_prefix("Long desc= ription: ") { + spec.long_description =3D Some(long_desc.to_string()); + } else if let Some(version) =3D line.strip_prefix("Version: ")= { + spec.version =3D Some(version.to_string()); + } else if let Some(since) =3D line.strip_prefix("Since: ") { + spec.since_version =3D Some(since.to_string()); + } else if let Some(flags) =3D line.strip_prefix("Context flags= : ") { + spec.context_flags =3D flags.split_whitespace().map(str::t= o_string).collect(); + } else if let Some(subsys) =3D line.strip_prefix("Subsystem: "= ) { + spec.subsystem =3D Some(subsys.to_string()); + } else if let Some(path) =3D line.strip_prefix("Sysfs Path: ")= { + spec.sysfs_path =3D Some(path.to_string()); + } else if let Some(perms) =3D line.strip_prefix("Permissions: = ") { + spec.permissions =3D Some(perms.to_string()); + } + } + + // Handle any remaining capability + if let Some(cap) =3D current_capability.take() { + spec.capabilities.push(cap); + } + + // Determine API type based on name + if api_name.starts_with("sys_") { + spec.api_type =3D "syscall".to_string(); + } else if api_name.contains("_ioctl") || api_name.starts_with("ioc= tl_") { + spec.api_type =3D "ioctl".to_string(); + } else if api_name.contains("sysfs") + || api_name.ends_with("_show") + || api_name.ends_with("_store") + { + spec.api_type =3D "sysfs".to_string(); + } else { + spec.api_type =3D "function".to_string(); + } + + Ok(spec) + } +} + +impl ApiExtractor for DebugfsExtractor { + fn extract_all(&self) -> Result> { + let api_names =3D self.parse_list_file()?; + let mut specs =3D Vec::new(); + + for name in api_names { + match self.parse_spec_file(&name) { + Ok(spec) =3D> specs.push(spec), + Err(_e) =3D> {} // Silently skip files that fail to parse + } + } + + Ok(specs) + } + + fn extract_by_name(&self, name: &str) -> Result> { + let api_names =3D 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) =3D self.extract_by_name(api_name)? { + display_api_spec(&spec, formatter, writer)?; + } else { + writeln!(writer, "API '{api_name}' not found in debugfs")?; + } + + Ok(()) + } +} diff --git a/tools/kapi/src/extractor/kerneldoc_parser.rs b/tools/kapi/src/= extractor/kerneldoc_parser.rs new file mode 100644 index 000000000000..2a6b5c896be9 --- /dev/null +++ b/tools/kapi/src/extractor/kerneldoc_parser.rs @@ -0,0 +1,694 @@ +use super::{ + ApiSpec, CapabilitySpec, ConstraintSpec, ErrorSpec, LockSpec, ParamSpe= c, + ReturnSpec, SideEffectSpec, SignalSpec, StateTransitionSpec, StructSpe= c, + StructFieldSpec, +}; +use anyhow::Result; +use std::collections::HashMap; + +/// Real kerneldoc parser that extracts KAPI annotations +pub struct KerneldocParserImpl; + +impl KerneldocParserImpl { + pub fn new() -> Self { + KerneldocParserImpl + } + + pub fn parse_kerneldoc( + &self, + doc: &str, + name: &str, + api_type: &str, + _signature: Option<&str>, + ) -> Result { + let mut spec =3D ApiSpec { + name: name.to_string(), + api_type: api_type.to_string(), + 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![], + struct_specs: vec![], + }; + + // Parse line by line + let lines: Vec<&str> =3D doc.lines().collect(); + let mut i =3D 0; + + // Extract main description from function name line + if let Some(first_line) =3D lines.first() { + if let Some((_, desc)) =3D first_line.split_once(" - ") { + spec.description =3D Some(desc.trim().to_string()); + } + } + + // Keep track of parameters we've seen + let mut param_map: HashMap =3D HashMap::new(); + let mut struct_fields: Vec =3D Vec::new(); + let mut current_lock: Option =3D None; + let mut current_signal: Option =3D None; + let mut current_capability: Option =3D None; + + while i < lines.len() { + let line =3D lines[i].trim(); + + // Skip empty lines + if line.is_empty() { + i +=3D 1; + continue; + } + + // Parse @param lines + if let Some(rest) =3D line.strip_prefix("@") { + if let Some((param_name, desc)) =3D rest.split_once(':') { + let param_name =3D param_name.trim(); + let desc =3D desc.trim(); + if !param_name.contains('-') { + // This is a basic parameter description - add to = map + param_map.insert(param_name.to_string(), ParamSpec= { + index: param_map.len() as u32, + name: param_name.to_string(), + type_name: String::new(), + description: desc.to_string(), + flags: 0, + param_type: 0, + constraint_type: 0, + constraint: None, + min_value: None, + max_value: None, + valid_mask: None, + enum_values: vec![], + size: None, + alignment: None, + }); + } + } + } + // Parse long-desc + else if let Some(rest) =3D line.strip_prefix("long-desc:") { + spec.long_description =3D Some(self.collect_multiline_valu= e(&lines, i, rest)); + } + // Parse context-flags + else if let Some(rest) =3D line.strip_prefix("context-flags:")= { + spec.context_flags =3D self.parse_context_flags(rest.trim(= )); + } + // Parse param-count + else if let Some(rest) =3D line.strip_prefix("param-count:") { + spec.param_count =3D rest.trim().parse().ok(); + } + // Parse param-type + else if let Some(rest) =3D line.strip_prefix("param-type:") { + let parts: Vec<&str> =3D rest.split(',').map(|s| s.trim())= .collect(); + if parts.len() >=3D 2 { + if let Some(param) =3D param_map.get_mut(parts[0]) { + param.param_type =3D self.parse_param_type(parts[1= ]); + } + } + } + // Parse param-flags + else if let Some(rest) =3D line.strip_prefix("param-flags:") { + let parts: Vec<&str> =3D rest.split(',').map(|s| s.trim())= .collect(); + if parts.len() >=3D 2 { + if let Some(param) =3D param_map.get_mut(parts[0]) { + param.flags =3D self.parse_param_flags(parts[1]); + } + } + } + // Parse param-range + else if let Some(rest) =3D line.strip_prefix("param-range:") { + let parts: Vec<&str> =3D rest.split(',').map(|s| s.trim())= .collect(); + if parts.len() >=3D 3 { + if let Some(param) =3D param_map.get_mut(parts[0]) { + param.min_value =3D parts[1].parse().ok(); + param.max_value =3D parts[2].parse().ok(); + param.constraint_type =3D 1; // KAPI_CONSTRAINT_RA= NGE + } + } + } + // Parse param-constraint + else if let Some(rest) =3D line.strip_prefix("param-constraint= :") { + let parts: Vec<&str> =3D rest.splitn(2, ',').map(|s| s.tri= m()).collect(); + if parts.len() >=3D 2 { + if let Some(param) =3D param_map.get_mut(parts[0]) { + param.constraint =3D Some(parts[1].to_string()); + } + } + } + // Parse error + else if let Some(rest) =3D line.strip_prefix("error:") { + // Parse error in format: "ERROR_CODE, description" + let parts: Vec<&str> =3D rest.splitn(2, ',').map(|s| s.tri= m()).collect(); + if parts.len() >=3D 2 { + let error_name =3D parts[0].to_string(); + let description =3D parts[1].to_string(); + + // Look for desc: line on the next line + let mut full_description =3D description; + if i + 1 < lines.len() { + if let Some(desc_line) =3D lines[i + 1].strip_pref= ix("* desc:") { + full_description =3D desc_line.trim().to_strin= g(); + } else if let Some(desc_line) =3D lines[i + 1].str= ip_prefix("* desc:") { + full_description =3D desc_line.trim().to_strin= g(); + } + } + + // Map common error names to codes + let error_code =3D match error_name.as_str() { + "E2BIG" =3D> -7, + "EACCES" =3D> -13, + "EAGAIN" =3D> -11, + "EBADF" =3D> -9, + "EBUSY" =3D> -16, + "EFAULT" =3D> -14, + "EINTR" =3D> -4, + "EINVAL" =3D> -22, + "EIO" =3D> -5, + "EISDIR" =3D> -21, + "ELIBBAD" =3D> -80, + "ELOOP" =3D> -40, + "EMFILE" =3D> -24, + "ENAMETOOLONG" =3D> -36, + "ENFILE" =3D> -23, + "ENOENT" =3D> -2, + "ENOEXEC" =3D> -8, + "ENOMEM" =3D> -12, + "ENOTDIR" =3D> -20, + "EOPNOTSUPP" =3D> -95, + "EPERM" =3D> -1, + "ESRCH" =3D> -3, + "ETXTBSY" =3D> -26, + _ =3D> 0, + }; + + spec.errors.push(ErrorSpec { + error_code, + name: error_name, + condition: String::new(), + description: full_description, + }); + } + } + // Parse lock + else if let Some(rest) =3D line.strip_prefix("lock:") { + // Save previous lock if any + if let Some(lock) =3D current_lock.take() { + spec.locks.push(lock); + } + + let parts: Vec<&str> =3D rest.split(',').map(|s| s.trim())= .collect(); + if parts.len() >=3D 2 { + current_lock =3D Some(LockSpec { + lock_name: parts[0].to_string(), + lock_type: self.parse_lock_type(parts[1]), + acquired: false, + released: false, + held_on_entry: false, + held_on_exit: false, + description: String::new(), + }); + } + } + // Parse lock attributes + else if line.strip_prefix("lock-acquired:").is_some() { + if let Some(lock) =3D current_lock.as_mut() { + lock.acquired =3D true; + } + } + else if line.strip_prefix("lock-released:").is_some() { + if let Some(lock) =3D current_lock.as_mut() { + lock.released =3D true; + } + } + else if let Some(rest) =3D line.strip_prefix("lock-desc:") { + if let Some(lock) =3D current_lock.as_mut() { + lock.description =3D self.collect_multiline_value(&lin= es, i, rest); + } + } + // Parse signal + else if let Some(rest) =3D line.strip_prefix("signal:") { + // Save previous signal if any + if let Some(signal) =3D current_signal.take() { + spec.signals.push(signal); + } + + let signal_name =3D rest.trim().to_string(); + current_signal =3D Some(SignalSpec { + signal_num: 0, + signal_name, + direction: 1, + action: 0, + target: None, + condition: None, + description: None, + restartable: false, + timing: 0, + priority: 0, + interruptible: false, + queue: None, + sa_flags: 0, + sa_flags_required: 0, + sa_flags_forbidden: 0, + state_required: 0, + state_forbidden: 0, + error_on_signal: None, + }); + } + // Parse signal attributes + else if let Some(rest) =3D line.strip_prefix("signal-direction= :") { + if let Some(signal) =3D current_signal.as_mut() { + signal.direction =3D self.parse_signal_direction(rest.= trim()); + } + } + else if let Some(rest) =3D line.strip_prefix("signal-action:")= { + if let Some(signal) =3D current_signal.as_mut() { + signal.action =3D self.parse_signal_action(rest.trim()= ); + } + } + else if let Some(rest) =3D line.strip_prefix("signal-condition= :") { + if let Some(signal) =3D current_signal.as_mut() { + signal.condition =3D Some(self.collect_multiline_value= (&lines, i, rest)); + } + } + else if let Some(rest) =3D line.strip_prefix("signal-desc:") { + if let Some(signal) =3D current_signal.as_mut() { + signal.description =3D Some(self.collect_multiline_val= ue(&lines, i, rest)); + } + } + else if let Some(rest) =3D line.strip_prefix("signal-timing:")= { + if let Some(signal) =3D current_signal.as_mut() { + signal.timing =3D self.parse_signal_timing(rest.trim()= ); + } + } + else if let Some(rest) =3D line.strip_prefix("signal-priority:= ") { + if let Some(signal) =3D current_signal.as_mut() { + signal.priority =3D rest.trim().parse().unwrap_or(0); + } + } + else if line.strip_prefix("signal-interruptible:").is_some() { + if let Some(signal) =3D current_signal.as_mut() { + signal.interruptible =3D true; + } + } + else if let Some(rest) =3D line.strip_prefix("signal-state-req= :") { + if let Some(signal) =3D current_signal.as_mut() { + signal.state_required =3D self.parse_signal_state(rest= .trim()); + } + } + // Parse side-effect + else if let Some(rest) =3D line.strip_prefix("side-effect:") { + let full_effect =3D self.collect_multiline_value(&lines, i= , rest); + let parts: Vec<&str> =3D full_effect.splitn(3, ',').map(|s= | s.trim()).collect(); + if parts.len() >=3D 3 { + let mut effect =3D SideEffectSpec { + effect_type: self.parse_effect_type(parts[0]), + target: parts[1].to_string(), + condition: None, + description: parts[2].to_string(), + reversible: false, + }; + + // Check for additional attributes + if let Some(pos) =3D parts[2].find("condition=3D") { + let cond_str =3D &parts[2][pos + 10..]; + if let Some(end) =3D cond_str.find(',') { + effect.condition =3D Some(cond_str[..end].to_s= tring()); + } else { + effect.condition =3D Some(cond_str.to_string()= ); + } + } + + if parts[2].contains("reversible=3Dyes") { + effect.reversible =3D true; + } + + spec.side_effects.push(effect); + } + } + // Parse state-trans + else if let Some(rest) =3D line.strip_prefix("state-trans:") { + let parts: Vec<&str> =3D rest.split(',').map(|s| s.trim())= .collect(); + if parts.len() >=3D 4 { + spec.state_transitions.push(StateTransitionSpec { + object: parts[0].to_string(), + from_state: parts[1].to_string(), + to_state: parts[2].to_string(), + condition: None, + description: parts[3].to_string(), + }); + } + } + // Parse capability + else if let Some(rest) =3D line.strip_prefix("capability:") { + // Save previous capability if any + if let Some(cap) =3D current_capability.take() { + spec.capabilities.push(cap); + } + + let parts: Vec<&str> =3D rest.split(',').map(|s| s.trim())= .collect(); + if parts.len() >=3D 3 { + current_capability =3D Some(CapabilitySpec { + capability: self.parse_capability_value(parts[0]), + action: parts[1].to_string(), + name: parts[2].to_string(), + allows: String::new(), + without_cap: String::new(), + check_condition: None, + priority: Some(0), + alternatives: vec![], + }); + } + } + // Parse capability attributes + else if let Some(rest) =3D line.strip_prefix("capability-allow= s:") { + if let Some(cap) =3D current_capability.as_mut() { + cap.allows =3D self.collect_multiline_value(&lines, i,= rest); + } + } + else if let Some(rest) =3D line.strip_prefix("capability-witho= ut:") { + if let Some(cap) =3D current_capability.as_mut() { + cap.without_cap =3D self.collect_multiline_value(&line= s, i, rest); + } + } + else if let Some(rest) =3D line.strip_prefix("capability-condi= tion:") { + if let Some(cap) =3D current_capability.as_mut() { + cap.check_condition =3D Some(self.collect_multiline_va= lue(&lines, i, rest)); + } + } + else if let Some(rest) =3D line.strip_prefix("capability-prior= ity:") { + if let Some(cap) =3D current_capability.as_mut() { + cap.priority =3D rest.trim().parse().ok(); + } + } + // Parse constraint + else if let Some(rest) =3D line.strip_prefix("constraint:") { + let parts: Vec<&str> =3D rest.splitn(2, ',').map(|s| s.tri= m()).collect(); + if parts.len() >=3D 2 { + spec.constraints.push(ConstraintSpec { + name: parts[0].to_string(), + description: parts[1].to_string(), + expression: None, + }); + } + } + // Parse constraint-expr + else if let Some(rest) =3D line.strip_prefix("constraint-expr:= ") { + let parts: Vec<&str> =3D rest.splitn(2, ',').map(|s| s.tri= m()).collect(); + if parts.len() >=3D 2 { + // Find matching constraint and update it + if let Some(constraint) =3D spec.constraints.iter_mut(= ).find(|c| c.name =3D=3D parts[0]) { + constraint.expression =3D Some(parts[1].to_string(= )); + } + } + } + // Parse struct-field + else if let Some(rest) =3D line.strip_prefix("struct-field:") { + let parts: Vec<&str> =3D rest.split(',').map(|s| s.trim())= .collect(); + if parts.len() >=3D 3 { + struct_fields.push(StructFieldSpec { + name: parts[0].to_string(), + field_type: self.parse_field_type(parts[1]), + type_name: parts[1].to_string(), + offset: 0, + size: 0, + flags: 0, + constraint_type: 0, + min_value: 0, + max_value: 0, + valid_mask: 0, + description: parts[2].to_string(), + }); + } + } + // Parse struct-field-range + else if let Some(rest) =3D line.strip_prefix("struct-field-ran= ge:") { + let parts: Vec<&str> =3D rest.split(',').map(|s| s.trim())= .collect(); + if parts.len() >=3D 3 { + // Update the field with range + if let Some(field) =3D struct_fields.iter_mut().find(|= f| f.name =3D=3D parts[0]) { + field.min_value =3D parts[1].parse().unwrap_or(0); + field.max_value =3D parts[2].parse().unwrap_or(0); + field.constraint_type =3D 1; // KAPI_CONSTRAINT_RA= NGE + } + } + } + // Parse examples + else if let Some(rest) =3D line.strip_prefix("examples:") { + spec.examples =3D Some(self.collect_multiline_value(&lines= , i, rest)); + } + // Parse notes + else if let Some(rest) =3D line.strip_prefix("notes:") { + spec.notes =3D Some(self.collect_multiline_value(&lines, i= , rest)); + } + // Parse since-version + else if let Some(rest) =3D line.strip_prefix("since-version:")= { + spec.since_version =3D Some(rest.trim().to_string()); + } + // Parse return-type + else if let Some(rest) =3D line.strip_prefix("return-type:") { + if spec.return_spec.is_none() { + spec.return_spec =3D Some(ReturnSpec { + type_name: rest.trim().to_string(), + description: String::new(), + return_type: self.parse_param_type(rest.trim()), + check_type: 0, + success_value: None, + success_min: None, + success_max: None, + error_values: vec![], + }); + } + } + // Parse return-check-type + else if let Some(rest) =3D line.strip_prefix("return-check-typ= e:") { + if let Some(ret) =3D spec.return_spec.as_mut() { + ret.check_type =3D self.parse_return_check_type(rest.t= rim()); + } + } + // Parse return-success + else if let Some(rest) =3D line.strip_prefix("return-success:"= ) { + if let Some(ret) =3D spec.return_spec.as_mut() { + ret.success_value =3D rest.trim().parse().ok(); + } + } + + i +=3D 1; + } + + // Save any remaining items + if let Some(lock) =3D current_lock { + spec.locks.push(lock); + } + if let Some(signal) =3D current_signal { + spec.signals.push(signal); + } + if let Some(cap) =3D current_capability { + spec.capabilities.push(cap); + } + + // Convert param_map to vec preserving order + let mut params: Vec =3D param_map.into_values().collect= (); + params.sort_by_key(|p| p.index); + spec.parameters =3D params; + + // Create struct spec if we have fields + if !struct_fields.is_empty() { + spec.struct_specs.push(StructSpec { + name: "struct sched_attr".to_string(), + size: 120, // Default for sched_attr + alignment: 8, + field_count: struct_fields.len() as u32, + fields: struct_fields, + description: "Structure specification".to_string(), + }); + } + + Ok(spec) + } + + fn collect_multiline_value(&self, lines: &[&str], start_idx: usize, fi= rst_part: &str) -> String { + let mut result =3D String::from(first_part.trim()); + let mut i =3D start_idx + 1; + + // Continue collecting lines until we hit another annotation or end + while i < lines.len() { + let line =3D lines[i]; + + // Stop if we hit another annotation (contains ':' and starts = with valid keyword) + if self.is_annotation_line(line) { + break; + } + + // Add continuation lines + if !line.trim().is_empty() && line.starts_with(" ") { + if !result.is_empty() { + result.push(' '); + } + result.push_str(line.trim()); + } else if line.trim().is_empty() { + // Empty line might be part of multiline + i +=3D 1; + continue; + } else { + // Non-continuation line, stop + break; + } + + i +=3D 1; + } + + result + } + + fn is_annotation_line(&self, line: &str) -> bool { + let annotations =3D [ + "param-", "error-", "lock", "signal", "side-effect:", + "state-trans:", "capability", "constraint", "struct-", + "return-", "examples:", "notes:", "since-", "context-", + "long-desc:" + ]; + + for ann in &annotations { + if line.trim_start().starts_with(ann) { + return true; + } + } + false + } + + fn parse_context_flags(&self, flags: &str) -> Vec { + flags.split('|') + .map(|f| f.trim().to_string()) + .collect() + } + + fn parse_param_type(&self, type_str: &str) -> u32 { + match type_str { + "KAPI_TYPE_INT" =3D> 1, + "KAPI_TYPE_UINT" =3D> 2, + "KAPI_TYPE_LONG" =3D> 3, + "KAPI_TYPE_ULONG" =3D> 4, + "KAPI_TYPE_STRING" =3D> 5, + "KAPI_TYPE_USER_PTR" =3D> 6, + _ =3D> 0, + } + } + + fn parse_field_type(&self, type_str: &str) -> u32 { + match type_str { + "__s32" | "int" =3D> 1, + "__u32" | "unsigned int" =3D> 2, + "__s64" | "long" =3D> 3, + "__u64" | "unsigned long" =3D> 4, + _ =3D> 0, + } + } + + fn parse_param_flags(&self, flags: &str) -> u32 { + let mut result =3D 0; + for flag in flags.split('|') { + match flag.trim() { + "KAPI_PARAM_IN" =3D> result |=3D 1, + "KAPI_PARAM_OUT" =3D> result |=3D 2, + "KAPI_PARAM_INOUT" =3D> result |=3D 3, + "KAPI_PARAM_USER" =3D> result |=3D 4, + _ =3D> {} + } + } + result + } + + fn parse_lock_type(&self, type_str: &str) -> u32 { + match type_str { + "KAPI_LOCK_SPINLOCK" =3D> 0, + "KAPI_LOCK_MUTEX" =3D> 1, + "KAPI_LOCK_RWLOCK" =3D> 2, + _ =3D> 3, + } + } + + fn parse_signal_direction(&self, dir: &str) -> u32 { + match dir { + "KAPI_SIGNAL_SEND" =3D> 1, + "KAPI_SIGNAL_RECEIVE" =3D> 2, + _ =3D> 0, + } + } + + fn parse_signal_action(&self, action: &str) -> u32 { + match action { + "KAPI_SIGNAL_ACTION_DEFAULT" =3D> 0, + "KAPI_SIGNAL_ACTION_IGNORE" =3D> 1, + "KAPI_SIGNAL_ACTION_CUSTOM" =3D> 2, + _ =3D> 0, + } + } + + fn parse_signal_timing(&self, timing: &str) -> u32 { + match timing { + "KAPI_SIGNAL_TIME_BEFORE" =3D> 0, + "KAPI_SIGNAL_TIME_DURING" =3D> 1, + "KAPI_SIGNAL_TIME_AFTER" =3D> 2, + _ =3D> 0, + } + } + + fn parse_signal_state(&self, state: &str) -> u32 { + match state { + "KAPI_SIGNAL_STATE_RUNNING" =3D> 1, + "KAPI_SIGNAL_STATE_SLEEPING" =3D> 2, + _ =3D> 0, + } + } + + fn parse_effect_type(&self, type_str: &str) -> u32 { + let mut result =3D 0; + for flag in type_str.split('|') { + match flag.trim() { + "KAPI_EFFECT_MODIFY_STATE" =3D> result |=3D 1, + "KAPI_EFFECT_PROCESS_STATE" =3D> result |=3D 2, + "KAPI_EFFECT_SCHEDULE" =3D> result |=3D 4, + _ =3D> {} + } + } + result + } + + fn parse_capability_value(&self, cap: &str) -> i32 { + match cap { + "CAP_SYS_NICE" =3D> 23, + _ =3D> 0, + } + } + + fn parse_return_check_type(&self, check: &str) -> u32 { + match check { + "KAPI_RETURN_ERROR_CHECK" =3D> 1, + "KAPI_RETURN_SUCCESS_CHECK" =3D> 2, + _ =3D> 0, + } + } +} \ 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 000000000000..010851fe6a89 --- /dev/null +++ b/tools/kapi/src/extractor/mod.rs @@ -0,0 +1,461 @@ +use crate::formatter::OutputFormatter; +use anyhow::Result; +use std::convert::TryInto; +use std::io::Write; + +pub mod debugfs; +pub mod kerneldoc_parser; +pub mod source_parser; +pub mod vmlinux; + +pub use debugfs::DebugfsExtractor; +pub use source_parser::SourceExtractor; +pub use vmlinux::VmlinuxExtractor; + +/// Socket state specification +#[derive(Debug, Clone, serde::Serialize)] +pub struct SocketStateSpec { + pub required_states: Vec, + pub forbidden_states: Vec, + pub resulting_state: Option, + pub condition: Option, + pub applicable_protocols: Option, +} + +/// Protocol behavior specification +#[derive(Debug, Clone, serde::Serialize)] +pub struct ProtocolBehaviorSpec { + pub applicable_protocols: String, + pub behavior: String, + pub protocol_flags: Option, + pub flag_description: Option, +} + +/// 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, + pub supports_wildcard: bool, + pub supports_multicast: bool, + pub supports_broadcast: bool, + pub special_addresses: Option, + pub port_range_min: u32, + pub port_range_max: u32, +} + +/// Buffer specification +#[derive(Debug, Clone, serde::Serialize)] +pub struct BufferSpec { + pub buffer_behaviors: Option, + pub min_buffer_size: Option, + pub max_buffer_size: Option, + pub optimal_buffer_size: Option, +} + +/// Async specification +#[derive(Debug, Clone, serde::Serialize)] +pub struct AsyncSpec { + pub supported_modes: Option, + pub nonblock_errno: Option, +} + +/// 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, + pub priority: Option, + pub alternatives: Vec, +} + +/// 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, + pub min_value: Option, + pub max_value: Option, + pub valid_mask: Option, + pub enum_values: Vec, + pub size: Option, + pub alignment: Option, +} + +/// 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, + pub success_min: Option, + pub success_max: Option, + pub error_values: Vec, +} + +/// 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, + pub condition: Option, + pub description: Option, + pub timing: u32, + pub priority: u32, + pub restartable: bool, + pub interruptible: bool, + pub queue: Option, + 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, +} + +/// 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, + 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, + pub description: String, +} + +/// Constraint specification +#[derive(Debug, Clone, serde::Serialize)] +pub struct ConstraintSpec { + pub name: String, + pub description: String, + pub expression: Option, +} + +/// 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, +} + +/// Struct field specification +#[derive(Debug, Clone, serde::Serialize)] +pub struct StructFieldSpec { + pub name: String, + pub field_type: u32, + pub type_name: String, + pub offset: usize, + pub size: usize, + pub flags: u32, + pub constraint_type: u32, + pub min_value: i64, + pub max_value: i64, + pub valid_mask: u64, + pub description: String, +} + +/// Struct specification +#[derive(Debug, Clone, serde::Serialize)] +pub struct StructSpec { + pub name: String, + pub size: usize, + pub alignment: usize, + pub field_count: u32, + pub fields: Vec, + 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, + pub long_description: Option, + pub version: Option, + pub context_flags: Vec, + pub param_count: Option, + pub error_count: Option, + pub examples: Option, + pub notes: Option, + pub since_version: Option, + // Sysfs-specific fields + pub subsystem: Option, + pub sysfs_path: Option, + pub permissions: Option, + // Networking-specific fields + pub socket_state: Option, + pub protocol_behaviors: Vec, + pub addr_families: Vec, + pub buffer_spec: Option, + pub async_spec: Option, + pub net_data_transfer: Option, + pub capabilities: Vec, + pub parameters: Vec, + pub return_spec: Option, + pub errors: Vec, + pub signals: Vec, + pub signal_masks: Vec, + pub side_effects: Vec, + pub state_transitions: Vec, + pub constraints: Vec, + pub locks: Vec, + pub struct_specs: Vec, +} + +/// Trait for extracting API specifications from different sources +pub trait ApiExtractor { + /// Extract all API specifications from the source + fn extract_all(&self) -> Result>; + + /// Extract a specific API specification by name + fn extract_by_name(&self, name: &str) -> Result>; + + /// 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) =3D &spec.description { + formatter.description(writer, desc)?; + } + + if let Some(long_desc) =3D &spec.long_description { + formatter.long_description(writer, long_desc)?; + } + + if let Some(version) =3D &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) =3D &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) =3D &spec.notes { + formatter.notes(writer, notes)?; + } + + if let Some(examples) =3D &spec.examples { + formatter.examples(writer, examples)?; + } + + // Display sysfs-specific fields + if spec.api_type =3D=3D "sysfs" { + if let Some(subsystem) =3D &spec.subsystem { + formatter.sysfs_subsystem(writer, subsystem)?; + } + if let Some(path) =3D &spec.sysfs_path { + formatter.sysfs_path(writer, path)?; + } + if let Some(perms) =3D &spec.permissions { + formatter.sysfs_permissions(writer, perms)?; + } + } + + // Display networking-specific fields + if let Some(socket_state) =3D &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) =3D &spec.buffer_spec { + formatter.buffer_spec(writer, buffer_spec)?; + } + + if let Some(async_spec) =3D &spec.async_spec { + formatter.async_spec(writer, async_spec)?; + } + + if let Some(net_data_transfer) =3D &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().unwr= ap_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_o= r(u32::MAX))?; + for lock in &spec.locks { + formatter.lock(writer, lock)?; + } + formatter.end_locks(writer)?; + } + + // Display struct specs + if !spec.struct_specs.is_empty() { + formatter.begin_struct_specs(writer, spec.struct_specs.len().try_i= nto().unwrap_or(u32::MAX))?; + for struct_spec in &spec.struct_specs { + formatter.struct_spec(writer, struct_spec)?; + } + formatter.end_struct_specs(writer)?; + } + + formatter.end_api_details(writer)?; + + Ok(()) +} diff --git a/tools/kapi/src/extractor/source_parser.rs b/tools/kapi/src/ext= ractor/source_parser.rs new file mode 100644 index 000000000000..7a72b85a83be --- /dev/null +++ b/tools/kapi/src/extractor/source_parser.rs @@ -0,0 +1,213 @@ +use super::{ + ApiExtractor, ApiSpec, display_api_spec, +}; +use super::kerneldoc_parser::KerneldocParserImpl; +use crate::formatter::OutputFormatter; +use anyhow::{Context, Result}; +use regex::Regex; +use std::fs; +use std::io::Write; +use std::path::Path; +use walkdir::WalkDir; + +/// Extractor for kernel source files with KAPI-annotated kerneldoc +pub struct SourceExtractor { + path: String, + parser: KerneldocParserImpl, + syscall_regex: Regex, + ioctl_regex: Regex, + function_regex: Regex, +} + +impl SourceExtractor { + pub fn new(path: &str) -> Result { + Ok(SourceExtractor { + path: path.to_string(), + parser: KerneldocParserImpl::new(), + syscall_regex: Regex::new(r"SYSCALL_DEFINE\d+\((\w+)")?, + ioctl_regex: Regex::new(r"(?:static\s+)?long\s+(\w+_ioctl)\s*\= (")?, + function_regex: Regex::new( + r"(?m)^(?:static\s+)?(?:inline\s+)?(?:(?:unsigned\s+)?(?:l= ong|int|void|char|short|struct\s+\w+\s*\*?|[\w_]+_t)\s*\*?\s+)?(\w+)\s*\([^= )]*\)", + )?, + }) + } + + fn extract_from_file(&self, path: &Path) -> Result> { + let content =3D fs::read_to_string(path) + .with_context(|| format!("Failed to read file: {}", path.displ= ay()))?; + + self.extract_from_content(&content) + } + + fn extract_from_content(&self, content: &str) -> Result> { + let mut specs =3D Vec::new(); + let mut in_kerneldoc =3D false; + let mut current_doc =3D String::new(); + let lines: Vec<&str> =3D content.lines().collect(); + let mut i =3D 0; + + while i < lines.len() { + let line =3D lines[i]; + + // Start of kerneldoc comment + if line.trim_start().starts_with("/**") { + in_kerneldoc =3D true; + current_doc.clear(); + i +=3D 1; + continue; + } + + // Inside kerneldoc comment + if in_kerneldoc { + if line.contains("*/") { + in_kerneldoc =3D false; + + // Check if this kerneldoc has KAPI annotations + if current_doc.contains("context-flags:") || + current_doc.contains("param-count:") || + current_doc.contains("side-effect:") || + current_doc.contains("state-trans:") || + current_doc.contains("error-code:") { + + // Look ahead for the function declaration + if let Some((name, api_type, signature)) =3D self.= find_function_after(&lines, i + 1) { + if let Ok(spec) =3D self.parser.parse_kerneldo= c(¤t_doc, &name, &api_type, Some(&signature)) { + specs.push(spec); + } + } + } + } else { + // Remove leading asterisk and preserve content + let cleaned =3D if let Some(stripped) =3D line.trim_st= art().strip_prefix("*") { + if let Some(no_space) =3D stripped.strip_prefix(' = ') { + no_space + } else { + stripped + } + } else { + line.trim_start() + }; + current_doc.push_str(cleaned); + current_doc.push('\n'); + } + } + + i +=3D 1; + } + + Ok(specs) + } + + fn find_function_after(&self, lines: &[&str], start: usize) -> Option<= (String, String, String)> { + for i in start..lines.len().min(start + 10) { + let line =3D lines[i]; + + // Skip empty lines + if line.trim().is_empty() { + continue; + } + + // Check for SYSCALL_DEFINE + if let Some(caps) =3D self.syscall_regex.captures(line) { + let name =3D format!("sys_{}", caps.get(1).unwrap().as_str= ()); + let signature =3D self.extract_syscall_signature(lines, i); + return Some((name, "syscall".to_string(), signature)); + } + + // Check for ioctl function + if let Some(caps) =3D self.ioctl_regex.captures(line) { + let name =3D caps.get(1).unwrap().as_str().to_string(); + return Some((name, "ioctl".to_string(), line.to_string())); + } + + // Check for regular function + if let Some(caps) =3D self.function_regex.captures(line) { + let name =3D caps.get(1).unwrap().as_str().to_string(); + return Some((name, "function".to_string(), line.to_string(= ))); + } + + // Stop if we hit something that's clearly not part of the fun= ction declaration + if !line.starts_with(' ') && !line.starts_with('\t') && !line.= trim().is_empty() { + break; + } + } + + None + } + + fn extract_syscall_signature(&self, lines: &[&str], start: usize) -> S= tring { + // Extract the full SYSCALL_DEFINE signature + let mut sig =3D String::new(); + let mut in_paren =3D false; + let mut paren_count =3D 0; + + for line in lines.iter().skip(start).take(20) { + let line =3D *line; + + // Start of SYSCALL_DEFINE + if line.contains("SYSCALL_DEFINE") { + if let Some(pos) =3D line.find('(') { + sig.push_str(&line[pos..]); + in_paren =3D true; + paren_count =3D line[pos..].chars().filter(|&c| c =3D= =3D '(').count() - + line[pos..].chars().filter(|&c| c =3D=3D= ')').count(); + } + } else if in_paren { + sig.push(' '); + sig.push_str(line.trim()); + paren_count +=3D line.chars().filter(|&c| c =3D=3D '(').co= unt(); + paren_count -=3D line.chars().filter(|&c| c =3D=3D ')').co= unt(); + + if paren_count =3D=3D 0 { + break; + } + } + } + + sig + } +} + +impl ApiExtractor for SourceExtractor { + fn extract_all(&self) -> Result> { + let path =3D Path::new(&self.path); + let mut all_specs =3D Vec::new(); + + if path.is_file() { + // Single file + all_specs.extend(self.extract_from_file(path)?); + } else if path.is_dir() { + // Directory - walk all .c files + for entry in WalkDir::new(path) + .into_iter() + .filter_map(|e| e.ok()) + .filter(|e| e.path().extension().is_some_and(|ext| ext =3D= =3D "c")) + { + if let Ok(specs) =3D self.extract_from_file(entry.path()) { + all_specs.extend(specs); + } + } + } + + Ok(all_specs) + } + + fn extract_by_name(&self, name: &str) -> Result> { + let all_specs =3D self.extract_all()?; + Ok(all_specs.into_iter().find(|s| s.name =3D=3D name)) + } + + fn display_api_details( + &self, + api_name: &str, + formatter: &mut dyn OutputFormatter, + output: &mut dyn Write, + ) -> Result<()> { + if let Some(spec) =3D self.extract_by_name(api_name)? { + display_api_spec(&spec, formatter, output)?; + } else { + writeln!(output, "API '{}' not found", api_name)?; + } + 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 000000000000..0a51943e1c02 --- /dev/null +++ b/tools/kapi/src/extractor/vmlinux/binary_utils.rs @@ -0,0 +1,180 @@ +// Constants for all structure field sizes +pub mod sizes { + pub const NAME: usize =3D 128; + pub const DESC: usize =3D 512; + pub const MAX_PARAMS: usize =3D 16; + pub const MAX_ERRORS: usize =3D 32; + pub const MAX_CONSTRAINTS: usize =3D 16; + pub const MAX_CAPABILITIES: usize =3D 8; + pub const MAX_SIGNALS: usize =3D 16; + pub const MAX_STRUCT_SPECS: usize =3D 8; + pub const MAX_SIDE_EFFECTS: usize =3D 32; + pub const MAX_STATE_TRANS: usize =3D 16; + pub const MAX_PROTOCOL_BEHAVIORS: usize =3D 8; + pub const MAX_ADDR_FAMILIES: usize =3D 8; +} + +// Helper for reading data at specific offsets +pub struct DataReader<'a> { + pub data: &'a [u8], + pub 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 <=3D self.data.len() { + let bytes =3D &self.data[self.pos..self.pos + len]; + self.pos +=3D len; + Some(bytes) + } else { + None + } + } + + pub fn read_cstring(&mut self, max_len: usize) -> Option { + let bytes =3D self.read_bytes(max_len)?; + if let Some(null_pos) =3D bytes.iter().position(|&b| b =3D=3D 0) { + if null_pos > 0 { + if let Ok(s) =3D std::str::from_utf8(&bytes[..null_pos]) { + return Some(s.to_string()); + } + } + } + None + } + + pub fn read_u32(&mut self) -> Option { + self.read_bytes(4).map(|b| u32::from_le_bytes(b.try_into().unwrap(= ))) + } + + pub fn read_u8(&mut self) -> Option { + self.read_bytes(1).map(|b| b[0]) + } + + pub fn read_i32(&mut self) -> Option { + self.read_bytes(4).map(|b| i32::from_le_bytes(b.try_into().unwrap(= ))) + } + + pub fn read_u64(&mut self) -> Option { + self.read_bytes(8).map(|b| u64::from_le_bytes(b.try_into().unwrap(= ))) + } + + pub fn read_i64(&mut self) -> Option { + self.read_bytes(8).map(|b| i64::from_le_bytes(b.try_into().unwrap(= ))) + } + + pub fn read_usize(&mut self) -> Option { + self.read_u64().map(|v| v as usize) + } + + pub fn skip(&mut self, len: usize) { + self.pos =3D (self.pos + len).min(self.data.len()); + } + + // Helper methods for common patterns + pub fn read_bool(&mut self) -> Option { + self.read_u8().map(|v| v !=3D 0) + } + + pub fn read_optional_string(&mut self, max_len: usize) -> Option { + self.read_cstring(max_len).filter(|s| !s.is_empty()) + } + + pub fn read_string_or_default(&mut self, max_len: usize) -> String { + self.read_cstring(max_len).unwrap_or_default() + } + + // Skip and discard - advances position by reading and discarding + pub fn discard_cstring(&mut self, max_len: usize) { + let _ =3D self.read_cstring(max_len); + } + + // Read multiple booleans at once + pub fn read_bools(&mut self) -> Option<[bool; N]> { + let mut result =3D [false; N]; + for item in &mut result { + *item =3D self.read_bool()?; + } + Some(result) + } + + +} + +// Structure layout definitions for calculating sizes +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 + // enum_values + 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) +} diff --git a/tools/kapi/src/extractor/vmlinux/magic_finder.rs b/tools/kapi/= src/extractor/vmlinux/magic_finder.rs new file mode 100644 index 000000000000..cb7dc535801a --- /dev/null +++ b/tools/kapi/src/extractor/vmlinux/magic_finder.rs @@ -0,0 +1,102 @@ +// Magic markers for each section +pub const MAGIC_PARAM: u32 =3D 0x4B415031; // 'KAP1' +pub const MAGIC_RETURN: u32 =3D 0x4B415232; // 'KAR2' +pub const MAGIC_ERROR: u32 =3D 0x4B414533; // 'KAE3' +pub const MAGIC_LOCK: u32 =3D 0x4B414C34; // 'KAL4' +pub const MAGIC_CONSTRAINT: u32 =3D 0x4B414335; // 'KAC5' +pub const MAGIC_INFO: u32 =3D 0x4B414936; // 'KAI6' +pub const MAGIC_SIGNAL: u32 =3D 0x4B415337; // 'KAS7' +pub const MAGIC_SIGMASK: u32 =3D 0x4B414D38; // 'KAM8' +pub const MAGIC_STRUCT: u32 =3D 0x4B415439; // 'KAT9' +pub const MAGIC_EFFECT: u32 =3D 0x4B414641; // 'KAFA' +pub const MAGIC_TRANS: u32 =3D 0x4B415442; // 'KATB' +pub const MAGIC_CAP: u32 =3D 0x4B414343; // 'KACC' + +pub struct MagicOffsets { + pub param_offset: Option, + pub return_offset: Option, + pub error_offset: Option, + pub lock_offset: Option, + pub constraint_offset: Option, + pub info_offset: Option, + pub signal_offset: Option, + pub sigmask_offset: Option, + pub struct_offset: Option, + pub effect_offset: Option, + pub trans_offset: Option, + pub cap_offset: Option, +} + +impl MagicOffsets { + /// Find magic markers in the provided data slice + /// data: slice of data to search (typically one spec's worth) + /// base_offset: absolute offset where this slice starts in the full b= uffer + pub fn find_in_data(data: &[u8], base_offset: usize) -> Self { + let mut offsets =3D MagicOffsets { + param_offset: None, + return_offset: None, + error_offset: None, + lock_offset: None, + constraint_offset: None, + info_offset: None, + signal_offset: None, + sigmask_offset: None, + struct_offset: None, + effect_offset: None, + trans_offset: None, + cap_offset: None, + }; + + // Scan through data looking for magic markers + // Only find the first occurrence of each magic to avoid cross-spe= c contamination + let mut i =3D 0; + while i + 4 <=3D data.len() { + let bytes =3D &data[i..i + 4]; + let value =3D u32::from_le_bytes([bytes[0], bytes[1], bytes[2]= , bytes[3]]); + + match value { + MAGIC_PARAM if offsets.param_offset.is_none() =3D> { + offsets.param_offset =3D Some(base_offset + i); + }, + MAGIC_RETURN if offsets.return_offset.is_none() =3D> { + offsets.return_offset =3D Some(base_offset + i); + }, + MAGIC_ERROR if offsets.error_offset.is_none() =3D> { + offsets.error_offset =3D Some(base_offset + i); + }, + MAGIC_LOCK if offsets.lock_offset.is_none() =3D> { + offsets.lock_offset =3D Some(base_offset + i); + }, + MAGIC_CONSTRAINT if offsets.constraint_offset.is_none() = =3D> { + offsets.constraint_offset =3D Some(base_offset + i); + }, + MAGIC_INFO if offsets.info_offset.is_none() =3D> { + offsets.info_offset =3D Some(base_offset + i); + }, + MAGIC_SIGNAL if offsets.signal_offset.is_none() =3D> { + offsets.signal_offset =3D Some(base_offset + i); + }, + MAGIC_SIGMASK if offsets.sigmask_offset.is_none() =3D> { + offsets.sigmask_offset =3D Some(base_offset + i); + }, + MAGIC_STRUCT if offsets.struct_offset.is_none() =3D> { + offsets.struct_offset =3D Some(base_offset + i); + }, + MAGIC_EFFECT if offsets.effect_offset.is_none() =3D> { + offsets.effect_offset =3D Some(base_offset + i); + }, + MAGIC_TRANS if offsets.trans_offset.is_none() =3D> { + offsets.trans_offset =3D Some(base_offset + i); + }, + MAGIC_CAP if offsets.cap_offset.is_none() =3D> { + offsets.cap_offset =3D Some(base_offset + i); + }, + _ =3D> {} + } + + i +=3D 1; + } + + offsets + } +} \ No newline at end of file diff --git a/tools/kapi/src/extractor/vmlinux/mod.rs b/tools/kapi/src/extra= ctor/vmlinux/mod.rs new file mode 100644 index 000000000000..a8c4ed4de626 --- /dev/null +++ b/tools/kapi/src/extractor/vmlinux/mod.rs @@ -0,0 +1,869 @@ +use super::{ + ApiExtractor, ApiSpec, CapabilitySpec, ConstraintSpec, ErrorSpec, Lock= Spec, ParamSpec, + ReturnSpec, SideEffectSpec, SignalMaskSpec, SignalSpec, StateTransitio= nSpec, StructSpec, + StructFieldSpec, +}; +use crate::formatter::OutputFormatter; +use anyhow::{Context, Result}; +use goblin::elf::Elf; +use std::convert::TryInto; +use std::fs; +use std::io::Write; + +mod binary_utils; +mod magic_finder; +use binary_utils::{ + DataReader, addr_family_spec_layout_size, async_spec_layout_size, buff= er_spec_layout_size, + protocol_behavior_spec_layout_size, signal_mask_spec_layout_size, + sizes, socket_state_spec_layout_size, struct_field_layout_size, +}; + +// Helper to convert empty strings to None +fn opt_string(s: String) -> Option { + if s.is_empty() { None } else { Some(s) } +} + +pub struct VmlinuxExtractor { + kapi_data: Vec, + specs: Vec, +} + +#[derive(Debug)] +struct KapiSpec { + name: String, + api_type: String, + offset: usize, +} + +impl VmlinuxExtractor { + pub fn new(vmlinux_path: &str) -> Result { + let vmlinux_data =3D fs::read(vmlinux_path) + .with_context(|| format!("Failed to read vmlinux file: {vmlinu= x_path}"))?; + + let elf =3D Elf::parse(&vmlinux_data).context("Failed to parse ELF= file")?; + + // Find the .kapi_specs section + let kapi_section =3D elf + .section_headers + .iter() + .find(|sh| { + if let Some(name) =3D elf.shdr_strtab.get_at(sh.sh_name) { + name =3D=3D ".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 =3D None; + let mut stop_addr =3D None; + + for sym in &elf.syms { + if let Some(name) =3D elf.strtab.get_at(sym.st_name) { + match name { + "__start_kapi_specs" =3D> start_addr =3D Some(sym.st_v= alue), + "__stop_kapi_specs" =3D> stop_addr =3D Some(sym.st_val= ue), + _ =3D> {} + } + } + } + + let start =3D start_addr.context("Could not find __start_kapi_spec= s symbol")?; + let stop =3D stop_addr.context("Could not find __stop_kapi_specs s= ymbol")?; + + if stop <=3D start { + anyhow::bail!("No kernel API specifications found in vmlinux"); + } + + // Calculate the offset within the file + let section_vaddr =3D kapi_section.sh_addr; + let file_offset =3D kapi_section.sh_offset + (start - section_vadd= r); + let data_size: usize =3D (stop - start) + .try_into() + .context("Data size too large for platform")?; + + let file_offset_usize: usize =3D 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 =3D vmlinux_data[file_offset_usize..(file_offset_usi= ze + data_size)].to_vec(); + + // Parse the specifications + let specs =3D parse_kapi_specs(&kapi_data)?; + + Ok(VmlinuxExtractor { kapi_data, specs }) + } +} + +fn parse_kapi_specs(data: &[u8]) -> Result> { + let mut specs =3D Vec::new(); + let mut offset =3D 0; + let mut last_found_offset =3D None; + + // Expected offset from struct start to param_magic based on struct la= yout + let param_magic_offset =3D sizes::NAME + 4 + sizes::DESC + (sizes::DES= C * 4) + 4; + + // Find specs by validating API name and magic marker pairs + while offset + param_magic_offset + 4 <=3D data.len() { + // Read potential API name + let name_bytes =3D &data[offset..offset + sizes::NAME.min(data.len= () - offset)]; + + // Find null terminator + let name_len =3D name_bytes.iter().position(|&b| b =3D=3D 0).unwra= p_or(0); + + if name_len > 0 && name_len < 100 { + let name =3D String::from_utf8_lossy(&name_bytes[..name_len]).= to_string(); + + // Validate API name format + if is_valid_api_name(&name) { + // Verify magic marker at expected position + let magic_offset =3D offset + param_magic_offset; + if magic_offset + 4 <=3D data.len() { + let magic_bytes =3D &data[magic_offset..magic_offset += 4]; + let magic_value =3D u32::from_le_bytes([magic_bytes[0]= , magic_bytes[1], magic_bytes[2], magic_bytes[3]]); + + if magic_value =3D=3D magic_finder::MAGIC_PARAM { + // Avoid duplicate detection of the same spec + if last_found_offset.is_none() || offset >=3D last= _found_offset.unwrap() + param_magic_offset { + let api_type =3D if name.starts_with("sys_") { + "syscall" + } else if name.ends_with("_ioctl") { + "ioctl" + } else if name.contains("sysfs") { + "sysfs" + } else { + "function" + } + .to_string(); + + specs.push(KapiSpec { + name: name.clone(), + api_type, + offset, + }); + + last_found_offset =3D Some(offset); + } + } + } + } + } + + // Scan byte by byte to find all specs + offset +=3D 1; + } + + Ok(specs) +} + + + + +fn is_valid_api_name(name: &str) -> bool { + // Validate API name format and length + if name.is_empty() || name.len() < 3 || name.len() > 100 { + return false; + } + + // Alphanumeric and underscore characters only + if !name.chars().all(|c| c.is_ascii_alphanumeric() || c =3D=3D '_') { + return false; + } + + // Must start with letter or underscore + let first_char =3D name.chars().next().unwrap(); + if !first_char.is_ascii_alphabetic() && first_char !=3D '_' { + return false; + } + + // Match common kernel API patterns + name.starts_with("sys_") || + name.starts_with("__") || + name.ends_with("_ioctl") || + name.contains("_") || + name.len() > 6 +} + +impl ApiExtractor for VmlinuxExtractor { + fn extract_all(&self) -> Result> { + Ok(self + .specs + .iter() + .map(|spec| { + // Parse the full spec for listing + parse_binary_to_api_spec(&self.kapi_data, spec.offset) + .unwrap_or_else(|_| 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![], + struct_specs: vec![], + }) + }) + .collect()) + } + + fn extract_by_name(&self, api_name: &str) -> Result> { + if let Some(spec) =3D self.specs.iter().find(|s| s.name =3D=3D api= _name) { + Ok(Some(parse_binary_to_api_spec(&self.kapi_data, spec.offset)= ?)) + } else { + Ok(None) + } + } + + fn display_api_details( + &self, + api_name: &str, + formatter: &mut dyn OutputFormatter, + writer: &mut dyn Write, + ) -> Result<()> { + if let Some(spec) =3D self.specs.iter().find(|s| s.name =3D=3D api= _name) { + let api_spec =3D parse_binary_to_api_spec(&self.kapi_data, spe= c.offset)?; + super::display_api_spec(&api_spec, formatter, writer)?; + } + Ok(()) + } +} + +/// Helper to read count and parse array items with optional magic offset +fn parse_array_with_magic( + reader: &mut DataReader, + magic_offset: Option, + max_items: u32, + parse_fn: F, +) -> Vec +where + F: Fn(&mut DataReader) -> Option, +{ + // Read count - position at magic+4 if magic offset exists + let count =3D if let Some(offset) =3D magic_offset { + reader.pos =3D offset + 4; + reader.read_u32() + } else { + reader.read_u32() + }; + + let mut items =3D Vec::new(); + if let Some(count) =3D count { + // Position at start of array data if magic offset exists + if let Some(offset) =3D magic_offset { + reader.pos =3D offset + 8; // +4 for magic, +4 for count + } + // Parse items up to max_items + for _ in 0..count.min(max_items) as usize { + if let Some(item) =3D parse_fn(reader) { + items.push(item); + } + } + } + items +} + +fn parse_binary_to_api_spec(data: &[u8], offset: usize) -> Result= { + let mut reader =3D DataReader::new(data, offset); + + // Search for magic markers in the entire spec data + let search_end =3D (offset + 0x70000).min(data.len()); // Search full = spec size + let spec_data =3D &data[offset..search_end]; + + // Find magic markers relative to the spec start + let magic_offsets =3D magic_finder::MagicOffsets::find_in_data(spec_da= ta, offset); + + // Read fields in exact order of struct kernel_api_spec + + // Read name (128 bytes) + let name =3D reader + .read_cstring(sizes::NAME) + .ok_or_else(|| anyhow::anyhow!("Failed to read API name"))?; + + // Determine API type + let api_type =3D if name.starts_with("sys_") { + "syscall" + } else if name.ends_with("_ioctl") { + "ioctl" + } else if name.contains("sysfs") { + "sysfs" + } else { + "function" + } + .to_string(); + + // Read version (u32) + let version =3D reader.read_u32().map(|v| v.to_string()); + + // Read description (512 bytes) + let description =3D reader.read_cstring(sizes::DESC).filter(|s| !s.is_= empty()); + + // Read long_description (2048 bytes) + let long_description =3D reader + .read_cstring(sizes::DESC * 4) + .filter(|s| !s.is_empty()); + + // Read context_flags (u32) + let context_flags =3D parse_context_flags(&mut reader); + + // Parse params array + let parameters =3D parse_array_with_magic( + &mut reader, + magic_offsets.param_offset, + sizes::MAX_PARAMS as u32, + |r| parse_param(r, 0), // Index doesn't seem to be used in parse_= param + ); + + // Read return_spec + let return_spec =3D parse_return_spec(&mut reader); + + // Parse errors array + let errors =3D parse_array_with_magic( + &mut reader, + magic_offsets.error_offset, + sizes::MAX_ERRORS as u32, + parse_error, + ); + + // Parse locks array + let locks =3D parse_array_with_magic( + &mut reader, + magic_offsets.lock_offset, + sizes::MAX_CONSTRAINTS as u32, + parse_lock, + ); + + // Parse constraints array + let constraints =3D parse_array_with_magic( + &mut reader, + magic_offsets.constraint_offset, + sizes::MAX_CONSTRAINTS as u32, + parse_constraint, + ); + + // Read examples and notes - position reader at info section if magic = found + let (examples, notes) =3D if let Some(info_offset) =3D magic_offsets.i= nfo_offset { + reader.pos =3D info_offset + 4; // +4 to skip magic + let examples =3D reader.read_cstring(sizes::DESC * 2).filter(|s| != s.is_empty()); + let notes =3D reader.read_cstring(sizes::DESC * 2).filter(|s| !s.i= s_empty()); + (examples, notes) + } else { + let examples =3D reader.read_cstring(sizes::DESC * 2).filter(|s| != s.is_empty()); + let notes =3D reader.read_cstring(sizes::DESC * 2).filter(|s| !s.i= s_empty()); + (examples, notes) + }; + + // Read since_version (32 bytes) + let since_version =3D reader.read_cstring(32).filter(|s| !s.is_empty()= ); + + // Skip deprecated (bool =3D 1 byte + 3 bytes padding) and replacement= (128 bytes) + // These fields were removed from kernel but we need to skip them for = binary compatibility + reader.skip(4); // deprecated + padding + reader.discard_cstring(sizes::NAME); // replacement + + // Parse signals array + let signals =3D parse_array_with_magic( + &mut reader, + magic_offsets.signal_offset, + sizes::MAX_SIGNALS as u32, + parse_signal, + ); + + // Read signal_mask_count (u32) + let signal_mask_count =3D reader.read_u32(); + + // Parse signal_masks array + let mut signal_masks =3D Vec::new(); + if let Some(count) =3D signal_mask_count { + for i in 0..sizes::MAX_SIGNALS { + if i < count as usize { + if let Some(mask) =3D parse_signal_mask(&mut reader) { + signal_masks.push(mask); + } + } else { + reader.skip(signal_mask_spec_layout_size()); + } + } + } else { + reader.skip(signal_mask_spec_layout_size() * sizes::MAX_SIGNALS); + } + + // Parse struct_specs array + let struct_specs =3D parse_array_with_magic( + &mut reader, + magic_offsets.struct_offset, + sizes::MAX_STRUCT_SPECS as u32, + parse_struct_spec, + ); + + // According to the C struct, the order is: + // side_effect_count, side_effects array, state_trans_count, state_tra= nsitions array, + // capability_count, capabilities array + + // Parse side_effects array + let side_effects =3D parse_array_with_magic( + &mut reader, + magic_offsets.effect_offset, + sizes::MAX_SIDE_EFFECTS as u32, + parse_side_effect, + ); + + // Parse state_transitions array + let state_transitions =3D parse_array_with_magic( + &mut reader, + magic_offsets.trans_offset, + sizes::MAX_STATE_TRANS as u32, + parse_state_transition, + ); + + // Parse capabilities array + let capabilities =3D parse_array_with_magic( + &mut reader, + magic_offsets.cap_offset, + sizes::MAX_CAPABILITIES as u32, + parse_capability, + ); + + // Skip remaining network/socket fields + reader.skip( + socket_state_spec_layout_size() + + protocol_behavior_spec_layout_size() * sizes::MAX_PROTOCOL_BEHAVIO= RS + + 4 + // protocol_behavior_count + buffer_spec_layout_size() + + async_spec_layout_size() + + addr_family_spec_layout_size() * sizes::MAX_ADDR_FAMILIES + + 4 + // addr_family_count + 6 + 2 + // 6 bool flags + padding + sizes::DESC * 3 // 3 semantic descriptions + ); + + Ok(ApiSpec { + name, + api_type, + description, + long_description, + version, + context_flags, + param_count: if parameters.is_empty() { None } else { Some(paramet= ers.len() as u32) }, + error_count: if errors.is_empty() { None } else { Some(errors.len(= ) as u32) }, + examples, + notes, + since_version, + 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, + parameters, + return_spec, + errors, + signals, + signal_masks, + side_effects, + state_transitions, + constraints, + locks, + struct_specs, + }) +} + +// Helper parsing functions + +fn parse_context_flags(reader: &mut DataReader) -> Vec { + const KAPI_CTX_PROCESS: u32 =3D 1 << 0; + const KAPI_CTX_SOFTIRQ: u32 =3D 1 << 1; + const KAPI_CTX_HARDIRQ: u32 =3D 1 << 2; + const KAPI_CTX_NMI: u32 =3D 1 << 3; + const KAPI_CTX_ATOMIC: u32 =3D 1 << 4; + const KAPI_CTX_SLEEPABLE: u32 =3D 1 << 5; + const KAPI_CTX_PREEMPT_DISABLED: u32 =3D 1 << 6; + const KAPI_CTX_IRQ_DISABLED: u32 =3D 1 << 7; + + if let Some(flags) =3D reader.read_u32() { + let mut parts =3D Vec::new(); + + if flags & KAPI_CTX_PROCESS !=3D 0 { + parts.push("KAPI_CTX_PROCESS"); + } + if flags & KAPI_CTX_SOFTIRQ !=3D 0 { + parts.push("KAPI_CTX_SOFTIRQ"); + } + if flags & KAPI_CTX_HARDIRQ !=3D 0 { + parts.push("KAPI_CTX_HARDIRQ"); + } + if flags & KAPI_CTX_NMI !=3D 0 { + parts.push("KAPI_CTX_NMI"); + } + if flags & KAPI_CTX_ATOMIC !=3D 0 { + parts.push("KAPI_CTX_ATOMIC"); + } + if flags & KAPI_CTX_SLEEPABLE !=3D 0 { + parts.push("KAPI_CTX_SLEEPABLE"); + } + if flags & KAPI_CTX_PREEMPT_DISABLED !=3D 0 { + parts.push("KAPI_CTX_PREEMPT_DISABLED"); + } + if flags & KAPI_CTX_IRQ_DISABLED !=3D 0 { + parts.push("KAPI_CTX_IRQ_DISABLED"); + } + + if !parts.is_empty() { + vec![parts.join(" | ")] + } else { + vec![] + } + } else { + vec![] + } +} + +fn parse_param(reader: &mut DataReader, index: usize) -> Option= { + let name =3D reader.read_cstring(sizes::NAME)?; + let type_name =3D reader.read_cstring(sizes::NAME)?; + let param_type =3D reader.read_u32()?; + let flags =3D reader.read_u32()?; + let size =3D reader.read_usize()?; + let alignment =3D reader.read_usize()?; + let min_value =3D reader.read_i64()?; + let max_value =3D reader.read_i64()?; + let valid_mask =3D reader.read_u64()?; + + // Skip enum_values pointer (8 bytes) + reader.skip(8); + let _enum_count =3D reader.read_u32()?; // Must use ? to propagate err= ors + let constraint_type =3D reader.read_u32()?; + // Skip validate function pointer (8 bytes) + reader.skip(8); + + let description =3D reader.read_string_or_default(sizes::DESC); + let constraint =3D reader.read_optional_string(sizes::DESC); + let _size_param_idx =3D reader.read_i32()?; // Must use ? to propagate= errors + let _size_multiplier =3D reader.read_usize()?; // Must use ? to propag= ate errors + + Some(ParamSpec { + index: index as u32, + name, + type_name, + description, + flags, + param_type, + constraint_type, + constraint, + min_value: Some(min_value), + max_value: Some(max_value), + valid_mask: Some(valid_mask), + enum_values: vec![], + size: Some(size as u32), + alignment: Some(alignment as u32), + }) +} + +fn parse_return_spec(reader: &mut DataReader) -> Option { + // Read type_name, but treat empty as valid (will be empty string) + let type_name =3D reader.read_string_or_default(sizes::NAME); + + // Read return_type and check_type + let return_type =3D reader.read_u32().unwrap_or(0); + let check_type =3D reader.read_u32().unwrap_or(0); + let success_value =3D reader.read_i64().unwrap_or(0); + let success_min =3D reader.read_i64().unwrap_or(0); + let success_max =3D reader.read_i64().unwrap_or(0); + + // Skip error_values pointer (8 bytes) + reader.skip(8); + let _error_count =3D reader.read_u32().unwrap_or(0); // Don't fail on = return spec + // Skip is_success function pointer (8 bytes) + reader.skip(8); + + let description =3D reader.read_string_or_default(sizes::DESC); + + // Return a spec even if type_name is empty, as long as we have some d= ata + // The type_name might be a string like "KAPI_TYPE_INT" that gets stor= ed literally + if type_name.is_empty() && return_type =3D=3D 0 && check_type =3D=3D 0= && success_value =3D=3D 0 { + // No return spec at all + return None; + } + + Some(ReturnSpec { + type_name, + description, + return_type, + check_type, + success_value: Some(success_value), + success_min: Some(success_min), + success_max: Some(success_max), + error_values: vec![], + }) +} + +fn parse_error(reader: &mut DataReader) -> Option { + let error_code =3D reader.read_i32()?; + let name =3D reader.read_cstring(sizes::NAME)?; + let condition =3D reader.read_string_or_default(sizes::DESC); + let description =3D reader.read_string_or_default(sizes::DESC); + + Some(ErrorSpec { + error_code, + name, + condition, + description, + }) +} + +fn parse_lock(reader: &mut DataReader) -> Option { + let lock_name =3D reader.read_cstring(sizes::NAME)?; + let lock_type =3D reader.read_u32()?; + let [acquired, released, held_on_entry, held_on_exit] =3D reader.read_= bools()?; + let description =3D reader.read_string_or_default(sizes::DESC); + + Some(LockSpec { + lock_name, + lock_type, + acquired, + released, + held_on_entry, + held_on_exit, + description, + }) +} + +fn parse_constraint(reader: &mut DataReader) -> Option { + let name =3D reader.read_cstring(sizes::NAME)?; + let description =3D reader.read_string_or_default(sizes::DESC); + let expression =3D reader.read_string_or_default(sizes::DESC); + + // No function pointer in packed struct + + Some(ConstraintSpec { + name, + description, + expression: opt_string(expression), + }) +} + +fn parse_signal(reader: &mut DataReader) -> Option { + let signal_num =3D reader.read_i32()?; + let signal_name =3D reader.read_cstring(32)?; // signal_name[32] + let direction =3D reader.read_u32()?; + let action =3D reader.read_u32()?; + let target =3D reader.read_optional_string(sizes::DESC); // target[512] + let condition =3D reader.read_optional_string(sizes::DESC); // conditi= on[512] + let description =3D reader.read_optional_string(sizes::DESC); // descr= iption[512] + let restartable =3D reader.read_bool()?; + let sa_flags_required =3D reader.read_u32()?; + let sa_flags_forbidden =3D reader.read_u32()?; + let error_on_signal =3D reader.read_i32()?; + let _transform_to =3D reader.read_i32()?; // transform_to + let timing_bytes =3D reader.read_bytes(32)?; // timing[32] + let timing =3D if let Some(end) =3D timing_bytes.iter().position(|&b| = b =3D=3D 0) { + String::from_utf8_lossy(&timing_bytes[..end]).parse().unwrap_or(0) + } else { + 0 + }; + let priority =3D reader.read_u8()?; + let interruptible =3D reader.read_bool()?; + let _queue_behavior =3D reader.read_bytes(128)?; // queue_behavior[128] + let state_required =3D reader.read_u32()?; + let state_forbidden =3D reader.read_u32()?; + + Some(SignalSpec { + signal_num, + signal_name, + direction, + action, + target, + condition, + description, + timing, + priority: priority as u32, + restartable, + interruptible, + queue: None, // queue_behavior not exposed in SignalSpec + sa_flags: 0, // Not directly available + sa_flags_required, + sa_flags_forbidden, + state_required, + state_forbidden, + error_on_signal: Some(error_on_signal), + }) +} + +fn parse_signal_mask(reader: &mut DataReader) -> Option { + let name =3D reader.read_cstring(sizes::NAME)?; + let description =3D reader.read_string_or_default(sizes::DESC); + + // Skip signals array + for _ in 0..sizes::MAX_SIGNALS { + reader.read_i32(); + } + + let _signal_count =3D reader.read_u32()?; + + Some(SignalMaskSpec { + name, + description, + }) +} + +fn parse_struct_field(reader: &mut DataReader) -> Option { + let name =3D reader.read_cstring(sizes::NAME)?; + let field_type =3D reader.read_u32()?; + let type_name =3D reader.read_cstring(sizes::NAME)?; + let offset =3D reader.read_usize()?; + let size =3D reader.read_usize()?; + let flags =3D reader.read_u32()?; + let constraint_type =3D reader.read_u32()?; + let min_value =3D reader.read_i64()?; + let max_value =3D reader.read_i64()?; + let valid_mask =3D reader.read_u64()?; + // Skip enum_values field (512 bytes) + let _enum_values =3D reader.read_cstring(sizes::DESC); // Don't fail o= n optional field + let description =3D reader.read_string_or_default(sizes::DESC); + + Some(StructFieldSpec { + name, + field_type, + type_name, + offset, + size, + flags, + constraint_type, + min_value, + max_value, + valid_mask, + description, + }) +} + +fn parse_struct_spec(reader: &mut DataReader) -> Option { + let name =3D reader.read_cstring(sizes::NAME)?; + let size =3D reader.read_usize()?; + let alignment =3D reader.read_usize()?; + let field_count =3D reader.read_u32()?; + + // Parse fields array + let mut fields =3D Vec::new(); + for _ in 0..field_count.min(sizes::MAX_PARAMS as u32) { + if let Some(field) =3D parse_struct_field(reader) { + fields.push(field); + } else { + // Skip this field if we can't parse it + reader.skip(struct_field_layout_size()); + } + } + + // Skip remaining fields if any + let remaining =3D sizes::MAX_PARAMS as u32 - field_count.min(sizes::MA= X_PARAMS as u32); + for _ in 0..remaining { + reader.skip(struct_field_layout_size()); + } + + let description =3D reader.read_string_or_default(sizes::DESC); + + Some(StructSpec { + name, + size, + alignment, + field_count, + fields, + description, + }) +} + +fn parse_side_effect(reader: &mut DataReader) -> Option { + let effect_type =3D reader.read_u32()?; + let target =3D reader.read_cstring(sizes::NAME)?; + let condition =3D reader.read_string_or_default(sizes::DESC); + let description =3D reader.read_string_or_default(sizes::DESC); + let reversible =3D reader.read_bool()?; + // No padding needed for packed struct + + Some(SideEffectSpec { + effect_type, + target, + condition: opt_string(condition), + description, + reversible, + }) +} + +fn parse_state_transition(reader: &mut DataReader) -> Option { + let from_state =3D reader.read_cstring(sizes::NAME)?; + let to_state =3D reader.read_cstring(sizes::NAME)?; + let condition =3D reader.read_string_or_default(sizes::DESC); + let object =3D reader.read_cstring(sizes::NAME)?; + let description =3D reader.read_string_or_default(sizes::DESC); + + Some(StateTransitionSpec { + object, + from_state, + to_state, + condition: opt_string(condition), + description, + }) +} + +fn parse_capability(reader: &mut DataReader) -> Option { + let capability =3D reader.read_i32()?; + let cap_name =3D reader.read_cstring(sizes::NAME)?; + let action =3D reader.read_u32()?; + let allows =3D reader.read_string_or_default(sizes::DESC); + let without_cap =3D reader.read_string_or_default(sizes::DESC); + let check_condition =3D reader.read_optional_string(sizes::DESC); + let priority =3D reader.read_u32()?; + + let mut alternatives =3D Vec::new(); + for _ in 0..sizes::MAX_CAPABILITIES { + if let Some(alt) =3D reader.read_i32() { + if alt !=3D 0 { + alternatives.push(alt); + } + } + } + + let _alternative_count =3D reader.read_u32()?; // alternative_count + + Some(CapabilitySpec { + capability, + name: cap_name, + action: action.to_string(), + allows, + without_cap, + check_condition, + priority: Some(priority as u8), + alternatives, + }) +} \ No newline at end of file diff --git a/tools/kapi/src/formatter/json.rs b/tools/kapi/src/formatter/js= on.rs new file mode 100644 index 000000000000..8025467409d6 --- /dev/null +++ b/tools/kapi/src/formatter/json.rs @@ -0,0 +1,468 @@ +use super::OutputFormatter; +use crate::extractor::{ + AddrFamilySpec, AsyncSpec, BufferSpec, CapabilitySpec, ConstraintSpec,= ErrorSpec, LockSpec, + ParamSpec, ProtocolBehaviorSpec, ReturnSpec, SideEffectSpec, SignalMas= kSpec, SignalSpec, + SocketStateSpec, StateTransitionSpec, StructSpec, +}; +use serde::Serialize; +use std::io::Write; + +pub struct JsonFormatter { + data: JsonData, +} + +#[derive(Serialize)] +struct JsonData { + #[serde(skip_serializing_if =3D "Option::is_none")] + apis: Option>, + #[serde(skip_serializing_if =3D "Option::is_none")] + api_details: Option, +} + +#[derive(Serialize)] +struct JsonApi { + name: String, + api_type: String, +} + +#[derive(Serialize)] +struct JsonApiDetails { + name: String, + #[serde(skip_serializing_if =3D "Option::is_none")] + description: Option, + #[serde(skip_serializing_if =3D "Option::is_none")] + long_description: Option, + #[serde(skip_serializing_if =3D "Vec::is_empty")] + context_flags: Vec, + #[serde(skip_serializing_if =3D "Option::is_none")] + examples: Option, + #[serde(skip_serializing_if =3D "Option::is_none")] + notes: Option, + #[serde(skip_serializing_if =3D "Option::is_none")] + since_version: Option, + // Sysfs-specific fields + #[serde(skip_serializing_if =3D "Option::is_none")] + subsystem: Option, + #[serde(skip_serializing_if =3D "Option::is_none")] + sysfs_path: Option, + #[serde(skip_serializing_if =3D "Option::is_none")] + permissions: Option, + // Networking-specific fields + #[serde(skip_serializing_if =3D "Option::is_none")] + socket_state: Option, + #[serde(skip_serializing_if =3D "Vec::is_empty")] + protocol_behaviors: Vec, + #[serde(skip_serializing_if =3D "Vec::is_empty")] + addr_families: Vec, + #[serde(skip_serializing_if =3D "Option::is_none")] + buffer_spec: Option, + #[serde(skip_serializing_if =3D "Option::is_none")] + async_spec: Option, + #[serde(skip_serializing_if =3D "Option::is_none")] + net_data_transfer: Option, + #[serde(skip_serializing_if =3D "Vec::is_empty")] + capabilities: Vec, + #[serde(skip_serializing_if =3D "Vec::is_empty")] + state_transitions: Vec, + #[serde(skip_serializing_if =3D "Vec::is_empty")] + side_effects: Vec, + #[serde(skip_serializing_if =3D "Vec::is_empty")] + parameters: Vec, + #[serde(skip_serializing_if =3D "Option::is_none")] + return_spec: Option, + #[serde(skip_serializing_if =3D "Vec::is_empty")] + errors: Vec, + #[serde(skip_serializing_if =3D "Vec::is_empty")] + locks: Vec, + #[serde(skip_serializing_if =3D "Vec::is_empty")] + struct_specs: Vec, + #[serde(skip_serializing_if =3D "Vec::is_empty")] + signals: Vec, + #[serde(skip_serializing_if =3D "Vec::is_empty")] + signal_masks: Vec, + #[serde(skip_serializing_if =3D "Vec::is_empty")] + constraints: Vec, +} + +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 =3D 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 =3D 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) =3D &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::i= o::Result<()> { + Ok(()) + } + + fn begin_api_details(&mut self, _w: &mut dyn Write, name: &str) -> std= ::io::Result<()> { + self.data.api_details =3D 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(), + struct_specs: Vec::new(), + signals: Vec::new(), + signal_masks: Vec::new(), + constraints: 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) =3D &mut self.data.api_details { + details.description =3D Some(desc.to_string()); + } + Ok(()) + } + + fn long_description(&mut self, _w: &mut dyn Write, desc: &str) -> std:= :io::Result<()> { + if let Some(details) =3D &mut self.data.api_details { + details.long_description =3D Some(desc.to_string()); + } + Ok(()) + } + + fn begin_context_flags(&mut self, _w: &mut dyn Write) -> std::io::Resu= lt<()> { + Ok(()) + } + + fn context_flag(&mut self, _w: &mut dyn Write, flag: &str) -> std::io:= :Result<()> { + if let Some(details) =3D &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) =3D &mut self.data.api_details { + details.examples =3D Some(examples.to_string()); + } + Ok(()) + } + + fn notes(&mut self, _w: &mut dyn Write, notes: &str) -> std::io::Resul= t<()> { + if let Some(details) =3D &mut self.data.api_details { + details.notes =3D Some(notes.to_string()); + } + Ok(()) + } + + fn since_version(&mut self, _w: &mut dyn Write, version: &str) -> std:= :io::Result<()> { + if let Some(details) =3D &mut self.data.api_details { + details.since_version =3D Some(version.to_string()); + } + Ok(()) + } + + fn sysfs_subsystem(&mut self, _w: &mut dyn Write, subsystem: &str) -> = std::io::Result<()> { + if let Some(details) =3D &mut self.data.api_details { + details.subsystem =3D Some(subsystem.to_string()); + } + Ok(()) + } + + fn sysfs_path(&mut self, _w: &mut dyn Write, path: &str) -> std::io::R= esult<()> { + if let Some(details) =3D &mut self.data.api_details { + details.sysfs_path =3D Some(path.to_string()); + } + Ok(()) + } + + fn sysfs_permissions(&mut self, _w: &mut dyn Write, perms: &str) -> st= d::io::Result<()> { + if let Some(details) =3D &mut self.data.api_details { + details.permissions =3D 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) =3D &mut self.data.api_details { + details.socket_state =3D 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) =3D &mut self.data.api_details { + details.protocol_behaviors.push(behavior.clone()); + } + Ok(()) + } + + fn end_protocol_behaviors(&mut self, _w: &mut dyn Write) -> std::io::R= esult<()> { + Ok(()) + } + + fn begin_addr_families(&mut self, _w: &mut dyn Write) -> std::io::Resu= lt<()> { + Ok(()) + } + + fn addr_family(&mut self, _w: &mut dyn Write, family: &AddrFamilySpec)= -> std::io::Result<()> { + if let Some(details) =3D &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) -> st= d::io::Result<()> { + if let Some(details) =3D &mut self.data.api_details { + details.buffer_spec =3D Some(spec.clone()); + } + Ok(()) + } + + fn async_spec(&mut self, _w: &mut dyn Write, spec: &AsyncSpec) -> std:= :io::Result<()> { + if let Some(details) =3D &mut self.data.api_details { + details.async_spec =3D Some(spec.clone()); + } + Ok(()) + } + + fn net_data_transfer(&mut self, _w: &mut dyn Write, desc: &str) -> std= ::io::Result<()> { + if let Some(details) =3D &mut self.data.api_details { + details.net_data_transfer =3D Some(desc.to_string()); + } + Ok(()) + } + + fn begin_capabilities(&mut self, _w: &mut dyn Write) -> std::io::Resul= t<()> { + Ok(()) + } + + fn capability(&mut self, _w: &mut dyn Write, cap: &CapabilitySpec) -> = std::io::Result<()> { + if let Some(details) =3D &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) =3D &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) =3D &mut self.data.api_details { + details.return_spec =3D Some(ret.clone()); + } + Ok(()) + } + + fn error(&mut self, _w: &mut dyn Write, error: &ErrorSpec) -> std::io:= :Result<()> { + if let Some(details) =3D &mut self.data.api_details { + details.errors.push(error.clone()); + } + Ok(()) + } + + fn begin_signals(&mut self, _w: &mut dyn Write, _count: u32) -> std::i= o::Result<()> { + Ok(()) + } + + fn signal(&mut self, _w: &mut dyn Write, signal: &SignalSpec) -> std::= io::Result<()> { + if let Some(api_details) =3D &mut self.data.api_details { + api_details.signals.push(signal.clone()); + } + 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) -> s= td::io::Result<()> { + Ok(()) + } + + fn signal_mask(&mut self, _w: &mut dyn Write, mask: &SignalMaskSpec) -= > std::io::Result<()> { + if let Some(api_details) =3D &mut self.data.api_details { + api_details.signal_masks.push(mask.clone()); + } + 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) -> s= td::io::Result<()> { + Ok(()) + } + + fn side_effect(&mut self, _w: &mut dyn Write, effect: &SideEffectSpec)= -> std::io::Result<()> { + if let Some(details) =3D &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) =3D &mut self.data.api_details { + details.state_transitions.push(trans.clone()); + } + Ok(()) + } + + fn end_state_transitions(&mut self, _w: &mut dyn Write) -> std::io::Re= sult<()> { + Ok(()) + } + + fn begin_constraints(&mut self, _w: &mut dyn Write, _count: u32) -> st= d::io::Result<()> { + Ok(()) + } + + fn constraint( + &mut self, + _w: &mut dyn Write, + constraint: &ConstraintSpec, + ) -> std::io::Result<()> { + if let Some(api_details) =3D &mut self.data.api_details { + api_details.constraints.push(constraint.clone()); + } + 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::Re= sult<()> { + if let Some(details) =3D &mut self.data.api_details { + details.locks.push(lock.clone()); + } + Ok(()) + } + + fn end_locks(&mut self, _w: &mut dyn Write) -> std::io::Result<()> { + Ok(()) + } + + fn begin_struct_specs(&mut self, _w: &mut dyn Write, _count: u32) -> s= td::io::Result<()> { + Ok(()) + } + + fn struct_spec(&mut self, _w: &mut dyn Write, spec: &StructSpec) -> st= d::io::Result<()> { + if let Some(ref mut details) =3D self.data.api_details { + details.struct_specs.push(spec.clone()); + } + Ok(()) + } + + fn end_struct_specs(&mut self, _w: &mut dyn Write) -> std::io::Result<= ()> { + Ok(()) + } +} diff --git a/tools/kapi/src/formatter/mod.rs b/tools/kapi/src/formatter/mod= .rs new file mode 100644 index 000000000000..d799ff0ba971 --- /dev/null +++ b/tools/kapi/src/formatter/mod.rs @@ -0,0 +1,145 @@ +use crate::extractor::{ + AddrFamilySpec, AsyncSpec, BufferSpec, CapabilitySpec, ConstraintSpec,= ErrorSpec, LockSpec, + ParamSpec, ProtocolBehaviorSpec, ReturnSpec, SideEffectSpec, SignalMas= kSpec, SignalSpec, + SocketStateSpec, StateTransitionSpec, StructSpec, +}; +use std::io::Write; + +mod json; +mod plain; +mod rst; +mod shall; + +pub use json::JsonFormatter; +pub use plain::PlainFormatter; +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 =3D String; + + fn from_str(s: &str) -> Result { + match s.to_lowercase().as_str() { + "plain" =3D> Ok(OutputFormat::Plain), + "json" =3D> Ok(OutputFormat::Json), + "rst" =3D> Ok(OutputFormat::Rst), + "shall" =3D> Ok(OutputFormat::Shall), + _ =3D> 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::i= o::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::R= esult<()>; + 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::Resul= t<()>; + 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) -> s= td::io::Result<()>; + fn sysfs_path(&mut self, w: &mut dyn Write, path: &str) -> std::io::Re= sult<()>; + 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::Re= sult<()>; + + fn begin_addr_families(&mut self, w: &mut dyn Write) -> std::io::Resul= t<()>; + 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) -> s= td::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::i= o::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::Res= ult<()>; + + // 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: &ConstraintSpe= c) + -> 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::R= esult<()>; + fn lock(&mut self, w: &mut dyn Write, lock: &LockSpec) -> std::io::Res= ult<()>; + fn end_locks(&mut self, w: &mut dyn Write) -> std::io::Result<()>; + + fn begin_struct_specs(&mut self, w: &mut dyn Write, count: u32) -> std= ::io::Result<()>; + fn struct_spec(&mut self, w: &mut dyn Write, spec: &StructSpec) -> std= ::io::Result<()>; + fn end_struct_specs(&mut self, w: &mut dyn Write) -> std::io::Result<(= )>; +} + +pub fn create_formatter(format: OutputFormat) -> Box { + match format { + OutputFormat::Plain =3D> Box::new(PlainFormatter::new()), + OutputFormat::Json =3D> Box::new(JsonFormatter::new()), + OutputFormat::Rst =3D> Box::new(RstFormatter::new()), + OutputFormat::Shall =3D> Box::new(ShallFormatter::new()), + } +} diff --git a/tools/kapi/src/formatter/plain.rs b/tools/kapi/src/formatter/p= lain.rs new file mode 100644 index 000000000000..4c2d930aac94 --- /dev/null +++ b/tools/kapi/src/formatter/plain.rs @@ -0,0 +1,558 @@ +use super::OutputFormatter; +use crate::extractor::{ + AddrFamilySpec, AsyncSpec, BufferSpec, CapabilitySpec, ConstraintSpec,= ErrorSpec, LockSpec, + ParamSpec, ProtocolBehaviorSpec, ReturnSpec, SideEffectSpec, SignalMas= kSpec, SignalSpec, + SocketStateSpec, StateTransitionSpec, +}; +use std::io::Write; + +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::i= o::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, "{}=3D", "=3D".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::R= esult<()> { + 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::Resul= t<()> { + 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 =3D Vec::new(); + if param.flags & 0x01 !=3D 0 { + flags.push("IN"); + } + if param.flags & 0x02 !=3D 0 { + flags.push("OUT"); + } + if param.flags & 0x04 !=3D 0 { + flags.push("INOUT"); + } + if param.flags & 0x08 !=3D 0 { + flags.push("USER"); + } + if param.flags & 0x10 !=3D 0 { + flags.push("OPTIONAL"); + } + if !flags.is_empty() { + writeln!(w, " Flags: {}", flags.join(" | "))?; + } + + // Display constraints + if let Some(constraint) =3D ¶m.constraint { + writeln!(w, " Constraint: {constraint}")?; + } + if let (Some(min), Some(max)) =3D (param.min_value, param.max_valu= e) { + writeln!(w, " Range: {min} to {max}")?; + } + if let Some(mask) =3D 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) =3D ret.success_value { + writeln!(w, " Success value: {val}")?; + } + if let (Some(min), Some(max)) =3D (ret.success_min, ret.success_ma= x) { + 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) -> s= td::io::Result<()> { + writeln!(w, "Subsystem: {subsystem}") + } + + fn sysfs_path(&mut self, w: &mut dyn Write, path: &str) -> std::io::Re= sult<()> { + 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) =3D &state.resulting_state { + writeln!(w, " Resulting state: {result}")?; + } + if let Some(cond) =3D &state.condition { + writeln!(w, " Condition: {cond}")?; + } + if let Some(protos) =3D &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) =3D &behavior.protocol_flags { + writeln!(w, " Flags: {flags}")?; + } + Ok(()) + } + + fn end_protocol_behaviors(&mut self, _w: &mut dyn Write) -> std::io::R= esult<()> { + Ok(()) + } + + fn begin_addr_families(&mut self, w: &mut dyn Write) -> std::io::Resul= t<()> { + 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) =3D &family.addr_format { + writeln!(w, " Format: {format}")?; + } + writeln!( + w, + " Features: wildcard=3D{}, multicast=3D{}, broadcast=3D{}", + family.supports_wildcard, family.supports_multicast, family.su= pports_broadcast + )?; + if let Some(special) =3D &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) =3D &spec.buffer_behaviors { + writeln!(w, " Behaviors: {behaviors}")?; + } + if let Some(min) =3D spec.min_buffer_size { + writeln!(w, " Min size: {min} bytes")?; + } + if let Some(max) =3D spec.max_buffer_size { + writeln!(w, " Max size: {max} bytes")?; + } + if let Some(optimal) =3D 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) =3D &spec.supported_modes { + writeln!(w, " Supported modes: {modes}")?; + } + if let Some(errno) =3D 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) -> s= td::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) =3D &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::i= o::Result<()> { + write!(w, " {} ({})", signal.signal_name, signal.signal_num)?; + + // Display direction + let direction =3D match signal.direction { + 0 =3D> "SEND", + 1 =3D> "RECEIVE", + 2 =3D> "HANDLE", + 3 =3D> "IGNORE", + _ =3D> "UNKNOWN", + }; + write!(w, " - {direction}")?; + + // Display action + let action =3D match signal.action { + 0 =3D> "DEFAULT", + 1 =3D> "TERMINATE", + 2 =3D> "COREDUMP", + 3 =3D> "STOP", + 4 =3D> "CONTINUE", + 5 =3D> "IGNORE", + 6 =3D> "CUSTOM", + 7 =3D> "DISCARD", + _ =3D> "UNKNOWN", + }; + writeln!(w, " - {action}")?; + + if let Some(target) =3D &signal.target { + writeln!(w, " Target: {target}")?; + } + if let Some(condition) =3D &signal.condition { + writeln!(w, " Condition: {condition}")?; + } + if let Some(desc) =3D &signal.description { + writeln!(w, " {desc}")?; + } + + // Display timing + let timing =3D match signal.timing { + 0 =3D> "BEFORE", + 1 =3D> "DURING", + 2 =3D> "AFTER", + 3 =3D> "EXIT", + _ =3D> "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) =3D 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) =3D &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) =3D &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::Re= sult<()> { + 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) =3D &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::R= esult<()> { + writeln!(w, "\nLocking Requirements ({count}):") + } + + fn lock(&mut self, w: &mut dyn Write, lock: &LockSpec) -> std::io::Res= ult<()> { + write!(w, " {}", lock.lock_name)?; + + // Display lock type + let lock_type =3D match lock.lock_type { + 0 =3D> "NONE", + 1 =3D> "MUTEX", + 2 =3D> "SPINLOCK", + 3 =3D> "RWLOCK", + 4 =3D> "SEQLOCK", + 5 =3D> "RCU", + 6 =3D> "SEMAPHORE", + 7 =3D> "CUSTOM", + _ =3D> "UNKNOWN", + }; + writeln!(w, " ({lock_type})")?; + + let mut actions =3D 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(()) + } + + fn begin_struct_specs(&mut self, w: &mut dyn Write, count: u32) -> std= ::io::Result<()> { + writeln!(w, "\nStructure Specifications ({count}):") + } + + fn struct_spec(&mut self, w: &mut dyn Write, spec: &crate::extractor::= StructSpec) -> std::io::Result<()> { + writeln!(w, " {} (size=3D{}, align=3D{}):", spec.name, spec.size,= spec.alignment)?; + if !spec.description.is_empty() { + writeln!(w, " {}", spec.description)?; + } + + if !spec.fields.is_empty() { + writeln!(w, " Fields ({}):", spec.field_count)?; + for field in &spec.fields { + write!(w, " - {} ({}):", field.name, field.type_nam= e)?; + if !field.description.is_empty() { + write!(w, " {}", field.description)?; + } + writeln!(w)?; + + // Show constraints if present + if field.min_value !=3D 0 || field.max_value !=3D 0 { + writeln!(w, " Range: [{}, {}]", field.min_val= ue, field.max_value)?; + } + if field.valid_mask !=3D 0 { + writeln!(w, " Mask: {:#x}", field.valid_mask)= ?; + } + } + } + Ok(()) + } + + fn end_struct_specs(&mut self, _w: &mut dyn Write) -> std::io::Result<= ()> { + Ok(()) + } +} diff --git a/tools/kapi/src/formatter/rst.rs b/tools/kapi/src/formatter/rst= .rs new file mode 100644 index 000000000000..51d0be911480 --- /dev/null +++ b/tools/kapi/src/formatter/rst.rs @@ -0,0 +1,621 @@ +use super::OutputFormatter; +use crate::extractor::{ + AddrFamilySpec, AsyncSpec, BufferSpec, CapabilitySpec, ConstraintSpec,= ErrorSpec, LockSpec, + ParamSpec, ProtocolBehaviorSpec, ReturnSpec, SideEffectSpec, SignalMas= kSpec, SignalSpec, + SocketStateSpec, StateTransitionSpec, +}; +use std::io::Write; + +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 =3D> '=3D', + 1 =3D> '-', + 2 =3D> '~', + 3 =3D> '^', + _ =3D> '"', + } + } +} + +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::i= o::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 =3D 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::R= esult<()> { + 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::Resul= t<()> { + self.current_section_level =3D 1; + let title =3D "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 =3D 1; + let title =3D 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 =3D 1; + let title =3D 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 =3D 1; + let title =3D "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 =3D 1; + let title =3D "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) -> s= td::io::Result<()> { + writeln!(w, ":Subsystem: {subsystem}")?; + writeln!(w) + } + + fn sysfs_path(&mut self, w: &mut dyn Write, path: &str) -> std::io::Re= sult<()> { + 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 =3D 1; + let title =3D "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) =3D &state.resulting_state { + writeln!(w, "**Resulting state:** {result}")?; + } + if let Some(cond) =3D &state.condition { + writeln!(w, "**Condition:** {cond}")?; + } + if let Some(protos) =3D &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 =3D 1; + let title =3D "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) =3D &behavior.protocol_flags { + writeln!(w)?; + writeln!(w, "*Flags:* {flags}")?; + } + writeln!(w) + } + + fn end_protocol_behaviors(&mut self, _w: &mut dyn Write) -> std::io::R= esult<()> { + Ok(()) + } + + fn begin_addr_families(&mut self, w: &mut dyn Write) -> std::io::Resul= t<()> { + self.current_section_level =3D 1; + let title =3D "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) =3D &family.addr_format { + writeln!(w, "* **Format:** ``{format}``")?; + } + writeln!( + w, + "* **Features:** wildcard=3D{}, multicast=3D{}, broadcast=3D{}= ", + family.supports_wildcard, family.supports_multicast, family.su= pports_broadcast + )?; + if let Some(special) =3D &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 =3D 1; + let title =3D "Buffer Specification"; + writeln!(w, "{title}")?; + writeln!( + w, + "{}", + Self::section_char(1).to_string().repeat(title.len()) + )?; + writeln!(w)?; + + if let Some(behaviors) =3D &spec.buffer_behaviors { + writeln!(w, "**Behaviors:** {behaviors}")?; + } + if let Some(min) =3D spec.min_buffer_size { + writeln!(w, "**Min size:** {min} bytes")?; + } + if let Some(max) =3D spec.max_buffer_size { + writeln!(w, "**Max size:** {max} bytes")?; + } + if let Some(optimal) =3D 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 =3D 1; + let title =3D "Asynchronous Operation"; + writeln!(w, "{title}")?; + writeln!( + w, + "{}", + Self::section_char(1).to_string().repeat(title.len()) + )?; + writeln!(w)?; + + if let Some(modes) =3D &spec.supported_modes { + writeln!(w, "**Supported modes:** {modes}")?; + } + if let Some(errno) =3D 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 =3D 1; + let title =3D "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) -> s= td::io::Result<()> { + writeln!(w, "**{} ({})** - {}", cap.name, cap.capability, cap.acti= on)?; + 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) =3D &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 =3D Vec::new(); + if param.flags & 0x01 !=3D 0 { + flags.push("IN"); + } + if param.flags & 0x02 !=3D 0 { + flags.push("OUT"); + } + if param.flags & 0x04 !=3D 0 { + flags.push("USER"); + } + if param.flags & 0x08 !=3D 0 { + flags.push("OPTIONAL"); + } + if !flags.is_empty() { + writeln!(w, " :Flags: {}", flags.join(", "))?; + } + + if let Some(constraint) =3D ¶m.constraint { + writeln!(w, " :Constraint: {}", constraint)?; + } + + if let (Some(min), Some(max)) =3D (param.min_value, param.max_valu= e) { + 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) =3D 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::i= o::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) -> s= td::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 =3D 1; + let title =3D 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) =3D &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 =3D 1; + let title =3D 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, + "* **{}**: {} =E2=86=92 {}", + trans.object, trans.from_state, trans.to_state + )?; + writeln!(w, " {}", trans.description)?; + if let Some(cond) =3D &trans.condition { + writeln!(w, " :Condition: {}", cond)?; + } + writeln!(w) + } + + fn end_state_transitions(&mut self, _w: &mut dyn Write) -> std::io::Re= sult<()> { + Ok(()) + } + + fn begin_constraints(&mut self, _w: &mut dyn Write, _count: u32) -> st= d::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::R= esult<()> { + self.current_section_level =3D 1; + let title =3D 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::Res= ult<()> { + write!(w, "* **{}**", lock.lock_name)?; + let lock_type_str =3D match lock.lock_type { + 1 =3D> " *(mutex)*", + 2 =3D> " *(spinlock)*", + 3 =3D> " *(rwlock)*", + 4 =3D> " *(semaphore)*", + 5 =3D> " *(RCU)*", + _ =3D> "", + }; + 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(()) + } + + fn begin_struct_specs(&mut self, w: &mut dyn Write, _count: u32) -> st= d::io::Result<()> { + writeln!(w)?; + writeln!(w, "Structure Specifications")?; + writeln!(w, "~~~~~~~~~~~~~~~~~~~~~~~")?; + writeln!(w) + } + + fn struct_spec(&mut self, w: &mut dyn Write, spec: &crate::extractor::= StructSpec) -> std::io::Result<()> { + writeln!(w, "**{}**", spec.name)?; + writeln!(w)?; + + if !spec.description.is_empty() { + writeln!(w, " {}", spec.description)?; + writeln!(w)?; + } + + writeln!(w, " :Size: {} bytes", spec.size)?; + writeln!(w, " :Alignment: {} bytes", spec.alignment)?; + writeln!(w, " :Fields: {}", spec.field_count)?; + writeln!(w)?; + + if !spec.fields.is_empty() { + for field in &spec.fields { + writeln!(w, " * **{}** ({})", field.name, field.type_name= )?; + if !field.description.is_empty() { + writeln!(w, " {}", field.description)?; + } + if field.min_value !=3D 0 || field.max_value !=3D 0 { + writeln!(w, " Range: [{}, {}]", field.min_value, fi= eld.max_value)?; + } + } + writeln!(w)?; + } + + Ok(()) + } + + fn end_struct_specs(&mut self, _w: &mut dyn Write) -> std::io::Result<= ()> { + Ok(()) + } +} diff --git a/tools/kapi/src/formatter/shall.rs b/tools/kapi/src/formatter/s= hall.rs new file mode 100644 index 000000000000..cc169d1290ca --- /dev/null +++ b/tools/kapi/src/formatter/shall.rs @@ -0,0 +1,891 @@ +use super::OutputFormatter; +use crate::extractor::{ + AddrFamilySpec, AsyncSpec, BufferSpec, CapabilitySpec, ConstraintSpec,= ErrorSpec, LockSpec, + ParamSpec, ProtocolBehaviorSpec, ReturnSpec, SideEffectSpec, SignalMas= kSpec, SignalSpec, + SocketStateSpec, StateTransitionSpec, +}; +use std::io::Write; + +pub struct ShallFormatter { + api_name: Option, + 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::i= o::Result<()> { + self.in_list =3D 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 =3D 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 =3D 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 =3D None; + Ok(()) + } + + fn description(&mut self, w: &mut dyn Write, desc: &str) -> std::io::R= esult<()> { + if let Some(api_name) =3D &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::Resul= t<()> { + 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 requir= ements + match flag { + "Process context" =3D> { + writeln!(w, "- The function shall be callable from process= context.")?; + writeln!( + w, + " Process context allows the function to sleep, alloc= ate memory with GFP_KERNEL, and access user space." + ) + } + "Softirq context" =3D> { + writeln!(w, "- The function shall be callable from softirq= context.")?; + writeln!( + w, + " In softirq context, the function shall not sleep an= d shall use GFP_ATOMIC for memory allocations." + ) + } + "Hardirq context" =3D> { + writeln!( + w, + "- The function shall be callable from hardirq (interr= upt) context." + )?; + writeln!( + w, + " In hardirq context, the function shall not sleep, s= hall minimize execution time, and shall use GFP_ATOMIC for allocations." + ) + } + "NMI context" =3D> { + writeln!( + w, + "- The function shall be callable from NMI (Non-Maskab= le Interrupt) context." + )?; + writeln!( + w, + " In NMI context, the function shall not take any loc= ks that might be held by interrupted code." + ) + } + "User mode" =3D> { + writeln!( + w, + "- The function shall be callable when the CPU is in u= ser mode." + )?; + writeln!(w, " This typically applies to system call entry= points.") + } + "Kernel mode" =3D> { + writeln!( + w, + "- The function shall be callable when the CPU is in k= ernel mode." + ) + } + "May sleep" =3D> { + writeln!(w, "- The function may sleep (block) during execu= tion.")?; + writeln!( + w, + " Callers shall ensure they are in a context where sl= eeping is allowed (not in interrupt or atomic context)." + ) + } + "Atomic context" =3D> { + 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" =3D> { + writeln!( + w, + "- The function shall be callable when preemption is e= nabled." + )?; + writeln!( + w, + " The function may be preempted by higher priority ta= sks." + ) + } + "Migration disabled" =3D> { + writeln!( + w, + "- The function shall be callable when CPU migration i= s disabled." + )?; + writeln!( + w, + " The function shall not rely on being able to migrat= e between CPUs." + ) + } + _ =3D> { + // Fallback for unrecognized flags + writeln!(w, "- The function shall be callable from {} cont= ext.", 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) =3D param.min_value { + if let Some(max) =3D param.max_value { + if min !=3D 0 || max !=3D 0 { + writeln!( + w, + "\n- If {} is less than {} or greater than {}, the= operation shall fail.", + param.name, min, max + )?; + } + } else if min !=3D 0 { + writeln!( + w, + "\n- If {} is less than {}, the operation shall fail.", + param.name, min + )?; + } + } else if let Some(max) =3D param.max_value { + if max !=3D 0 { + writeln!( + w, + "\n- If {} is greater than {}, the operation shall fai= l.", + param.name, max + )?; + } + } + + if let Some(constraint) =3D ¶m.constraint { + if !constraint.is_empty() { + let constraint_text =3D constraint.trim_end_matches('.'); + // Handle constraints that start with "Must be" or similar + if constraint_text.to_lowercase().starts_with("must be ") { + let requirement =3D &constraint_text[8..]; // Skip "Mu= st 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 =3D &constraint_text[5..]; // Skip "Mu= st " + 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, c= onstraint_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) =3D param.valid_mask { + if mask !=3D 0 { + writeln!( + w, + "\n- If {} contains bits not set in 0x{:x}, the operat= ion 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) =3D ret.success_value { + writeln!( + w, + "- If the operation succeeds, the function shall return {}= .", + success + )?; + } else if let Some(min) =3D ret.success_min { + if let Some(max) =3D ret.success_max { + writeln!( + w, + "- If the operation succeeds, the function shall retur= n a value between {} and {} inclusive.", + min, max + )?; + } else { + writeln!( + w, + "- If the operation succeeds, the function shall retur= n 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 =3D 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 !=3D error.c= ondition { + 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 requ= irement + let sentences: Vec<&str> =3D notes.split(". ").filter(|s| !s.trim(= ).is_empty()).collect(); + + for sentence in sentences { + let trimmed =3D 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.", t= rimmed)?; + } else { + // Convert to a shall statement + writeln!( + w, + "- The implementation shall ensure that {}.", + trimmed + .chars() + .next() + .unwrap() + .to_lowercase() + .collect::() + + &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 avail= able.", + version + ) + } + + fn sysfs_subsystem(&mut self, w: &mut dyn Write, subsystem: &str) -> s= td::io::Result<()> { + writeln!( + w, + "- If accessed through sysfs, the attribute shall be located i= n the {} subsystem.", + subsystem + ) + } + + fn sysfs_path(&mut self, w: &mut dyn Write, path: &str) -> std::io::Re= sult<()> { + 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 =3D state.required_states.join(" or "); + writeln!( + w, + "- If the socket is not in {} state, the operation shall f= ail.", + 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) =3D &state.resulting_state { + writeln!( + w, + "\n- If the operation succeeds, the socket state shall tra= nsition 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) =3D &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::R= esult<()> { + Ok(()) + } + + fn begin_addr_families(&mut self, w: &mut dyn Write) -> std::io::Resul= t<()> { + 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 sh= all 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) =3D spec.min_buffer_size { + writeln!( + w, + "- If the buffer size is less than {} bytes, the operation= shall fail.", + min + )?; + } + + if let Some(max) =3D spec.max_buffer_size { + writeln!( + w, + "\n- If the buffer size exceeds {} bytes, the excess data = shall be truncated.", + max + )?; + } + + if let Some(behaviors) =3D &spec.buffer_behaviors { + writeln!( + w, + "\n- When handling buffers, the following behavior shall a= pply: {}.", + 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) =3D &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) =3D 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) -> s= td::io::Result<()> { + writeln!(w)?; + writeln!( + w, + "- If the process attempts to {}, {} capability shall be check= ed.", + 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_c= ap)?; + + 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::i= o::Result<()> { + writeln!(w)?; + + // Skip signals with no meaningful description + if let Some(desc) =3D &signal.description { + if !desc.is_empty() { + writeln!(w, "- {}: {}.", signal.signal_name, desc)?; + return Ok(()); + } + } + + // Default behavior based on direction + if signal.direction =3D=3D 1 { + // Sends + writeln!( + w, + "- If the conditions for {} are met, the signal shall be s= ent to the target process.", + signal.signal_name + )?; + } else if signal.direction =3D=3D 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 oper= ation 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) =3D signal.error_on_signal { + if errno !=3D 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 operation= s:" + )?; + } + 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) -> st= d::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) =3D &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 {} sha= ll 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) =3D &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::Re= sult<()> { + 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) =3D &constraint.expression { + if expr.is_empty() { + writeln!(w, "- {}: {}.", constraint.name, constraint.descr= iption)?; + } else { + writeln!( + w, + "- If {} is violated, the operation shall fail.", + constraint.name + )?; + writeln!(w, " Constraint: {}", expr)?; + } + } else { + writeln!(w, "- {}: {}.", constraint.name, constraint.descripti= on)?; + } + + 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::Res= ult<()> { + writeln!(w)?; + + // Always show lock information if we have a description + if !lock.description.is_empty() { + let lock_type_str =3D match lock.lock_type { + 1 =3D> "mutex", + 2 =3D> "spinlock", + 3 =3D> "rwlock", + 4 =3D> "semaphore", + 5 =3D> "RCU", + _ =3D> "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 ac= quired.", + 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(()) + } + + fn begin_struct_specs(&mut self, _w: &mut dyn Write, _count: u32) -> s= td::io::Result<()> { + Ok(()) + } + + fn struct_spec(&mut self, w: &mut dyn Write, spec: &crate::extractor::= StructSpec) -> std::io::Result<()> { + writeln!(w, "[STRUCT_SPEC] The system SHALL define a structure '{}= ' with the following properties:", spec.name)?; + + if !spec.description.is_empty() { + writeln!(w, " [DESCRIPTION] {}", spec.description)?; + } + + writeln!(w, " [SIZE] The structure SHALL have a size of {} bytes"= , spec.size)?; + writeln!(w, " [ALIGNMENT] The structure SHALL have an alignment o= f {} bytes", spec.alignment)?; + + if !spec.fields.is_empty() { + writeln!(w, " [FIELDS] The structure SHALL contain {} fields:= ", spec.field_count)?; + for field in &spec.fields { + writeln!(w, " - Field '{}' of type '{}': {}", + field.name, field.type_name, field.description)?; + + if field.min_value !=3D 0 || field.max_value !=3D 0 { + writeln!(w, " [RANGE] SHALL be in range [{}, {}]", + field.min_value, field.max_value)?; + } + } + } + + writeln!(w)?; + Ok(()) + } + + fn end_struct_specs(&mut self, _w: &mut dyn Write) -> std::io::Result<= ()> { + Ok(()) + } +} diff --git a/tools/kapi/src/main.rs b/tools/kapi/src/main.rs new file mode 100644 index 000000000000..2d219046f328 --- /dev/null +++ b/tools/kapi/src/main.rs @@ -0,0 +1,116 @@ +//! kapi - Kernel API Specification Tool +//! +//! This tool extracts and displays kernel API specifications from multipl= e 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 extractor; +mod formatter; + +use extractor::{ApiExtractor, DebugfsExtractor, SourceExtractor, VmlinuxEx= tractor}; +use formatter::{OutputFormat, create_formatter}; + +#[derive(Parser, Debug)] +#[command(author, version, about, long_about =3D None)] +struct Args { + /// Path to the vmlinux file + #[arg(long, value_name =3D "PATH", group =3D "input")] + vmlinux: Option, + + /// Path to kernel source directory or file + #[arg(long, value_name =3D "PATH", group =3D "input")] + source: Option, + + /// Path to debugfs (defaults to /sys/kernel/debug if not specified) + #[arg(long, value_name =3D "PATH", group =3D "input")] + debugfs: Option, + + /// Optional: Name of specific API to show details for + api_name: Option, + + /// Output format + #[arg(long, short =3D 'f', default_value =3D "plain")] + format: String, +} + +fn main() -> Result<()> { + let args =3D Args::parse(); + + let output_format: OutputFormat =3D args + .format + .parse() + .map_err(|e: String| anyhow::anyhow!(e))?; + + let extractor: Box =3D match (args.vmlinux, args.sou= rce, args.debugfs.clone()) { + (Some(vmlinux_path), None, None) =3D> Box::new(VmlinuxExtractor::n= ew(&vmlinux_path)?), + (None, Some(source_path), None) =3D> Box::new(SourceExtractor::new= (&source_path)?), + (None, None, Some(_) | None) =3D> { + // If debugfs is specified or no input is provided, use debugfs + Box::new(DebugfsExtractor::new(args.debugfs)?) + } + _ =3D> { + 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, + output_format: OutputFormat, +) -> Result<()> { + let mut formatter =3D create_formatter(output_format); + let mut stdout =3D io::stdout(); + + formatter.begin_document(&mut stdout)?; + + if let Some(api_name_req) =3D api_name { + // Use the extractor to display API details + if let Some(_spec) =3D extractor.extract_by_name(&api_name_req)? { + extractor.display_api_details(&api_name_req, &mut *formatter, = &mut stdout)?; + } else if output_format =3D=3D 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 =3D extractor.extract_all()?; + + // Helper to display API list for a specific type + let mut display_api_type =3D |api_type: &str, title: &str| -> Resu= lt<()> { + let filtered: Vec<_> =3D all_specs.iter() + .filter(|s| s.api_type =3D=3D api_type) + .collect(); + + if !filtered.is_empty() { + formatter.begin_api_list(&mut stdout, title)?; + for spec in filtered { + formatter.api_item(&mut stdout, &spec.name, &spec.api_= type)?; + } + formatter.end_api_list(&mut stdout)?; + } + Ok(()) + }; + + display_api_type("syscall", "System Calls")?; + display_api_type("ioctl", "IOCTLs")?; + display_api_type("function", "Functions")?; + display_api_type("sysfs", "Sysfs Attributes")?; + + formatter.total_specs(&mut stdout, all_specs.len())?; + } + + formatter.end_document(&mut stdout)?; + + Ok(()) +} --=20 2.50.1