summaryrefslogtreecommitdiff
path: root/tools/kapi/src/main.rs
blob: 93ed11b508e9fdbb67e58b9fd7aff984ba02b70d (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
// SPDX-License-Identifier: GPL-2.0
// Copyright (C) 2026 Sasha Levin <sashal@kernel.org>

//! kapi - Kernel API Specification Tool
//!
//! This tool extracts and displays kernel API specifications from multiple sources:
//! - Kernel source code (kerneldoc blocks)
//! - 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, VmlinuxExtractor};
use formatter::{create_formatter, OutputFormat};

#[derive(Parser, Debug)]
#[command(author, version, about, long_about = None)]
struct Args {
    /// Path to the vmlinux file
    #[arg(long, value_name = "PATH", group = "input")]
    vmlinux: Option<String>,

    /// Path to kernel source directory or file
    #[arg(long, value_name = "PATH", group = "input")]
    source: Option<String>,

    /// Path to debugfs (defaults to /sys/kernel/debug if not specified)
    #[arg(long, value_name = "PATH", group = "input")]
    debugfs: Option<String>,

    /// Optional: Name of specific API to show details for
    api_name: Option<String>,

    /// Output format
    #[arg(long, short = 'f', default_value = "plain")]
    format: String,
}

fn main() -> Result<()> {
    let args = Args::parse();

    let output_format: OutputFormat = args
        .format
        .parse()
        .map_err(|e: String| anyhow::anyhow!(e))?;

    let extractor: Box<dyn ApiExtractor> = match (&args.vmlinux, &args.source, &args.debugfs) {
        (Some(vmlinux_path), None, None) => Box::new(VmlinuxExtractor::new(vmlinux_path)?),
        (None, Some(source_path), None) => Box::new(SourceExtractor::new(source_path)?),
        (None, None, Some(_) | None) => {
            // If debugfs is specified or no input is provided, use debugfs
            Box::new(DebugfsExtractor::new(args.debugfs.clone())?)
        }
        _ => {
            anyhow::bail!("Please specify only one of --vmlinux, --source, or --debugfs")
        }
    };

    display_apis(extractor.as_ref(), args.api_name, output_format)
}

fn display_apis(
    extractor: &dyn ApiExtractor,
    api_name: Option<String>,
    output_format: OutputFormat,
) -> Result<()> {
    let mut formatter = create_formatter(output_format);
    let mut stdout = io::stdout();

    formatter.begin_document(&mut stdout)?;

    if let Some(api_name_req) = api_name {
        // Use the extractor to display API details
        if let Some(_spec) = extractor.extract_by_name(&api_name_req)? {
            extractor.display_api_details(&api_name_req, &mut *formatter, &mut stdout)?;
        } else {
            eprintln!("API '{}' not found.", api_name_req);
            if output_format == OutputFormat::Plain {
                writeln!(stdout, "\nAvailable APIs:")?;
                for spec in extractor.extract_all()? {
                    writeln!(stdout, "  {} ({})", spec.name, spec.api_type)?;
                }
            }
            std::process::exit(1);
        }
    } else {
        // Display list of APIs using the extractor
        let all_specs = extractor.extract_all()?;

        // Helper to display API list for a specific type
        let mut display_api_type = |api_type: &str, title: &str| -> Result<()> {
            let filtered: Vec<_> = all_specs
                .iter()
                .filter(|s| s.api_type == 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(())
}