indexbus_inspect/
main.rs

1//! Inspect an IndexBus mapped region file.
2//!
3//! `indexbus-inspect` is a debugging/diagnostics CLI intended to help developers understand what
4//! a region file contains (layout header, capabilities, derived offsets, selected values).
5//!
6//! # Output stability
7//!
8//! - Plain text output is intended for humans and may change.
9//! - `--json` output is intended for machine parsing.
10//!
11//! ## JSON contract
12//!
13//! The JSON output is a **single object** printed on stdout.
14//!
15//! Stability rules within this workspace:
16//! - Existing field names and the top-level structure are treated as stable.
17//! - New fields may be added over time.
18//! - Values may change as the underlying ABI/layout evolves.
19
20#![deny(missing_docs)]
21
22mod errors;
23mod internal;
24
25use std::path::PathBuf;
26
27use crate::internal::inspect::inspect_path;
28use crate::internal::journal_dump::{dump_journal_path, watch_journal_path};
29
30pub use errors::Error;
31
32fn print_help() {
33    eprint!(
34        "indexbus-inspect: inspect an IndexBus mapped region file\n\nUSAGE:\n  indexbus-inspect <path> [--json] [--journal-dump] [--journal-watch]\n                  [--start-pos <u64>] [--max-records <n>] [--interval-ms <n>]\n\nFLAGS:\n  --json           Emit a single JSON object\n  --journal-dump   For journal regions: dump record headers/positions\n  --journal-watch  For journal regions: print bytes/sec and records/sec\n\nOPTIONS:\n  --start-pos <u64>      Start position for --journal-dump (default: 0)\n  --max-records <n>      Max records to print for --journal-dump (default: 128)\n  --interval-ms <n>      Polling interval for --journal-watch (default: 1000)\n\n"
35    );
36}
37
38fn main() -> Result<(), Error> {
39    let mut args = std::env::args().skip(1);
40    let mut path: Option<PathBuf> = None;
41    let mut json = false;
42    let mut journal_dump = false;
43    let mut journal_watch = false;
44    let mut start_pos: u64 = 0;
45    let mut max_records: usize = 128;
46    let mut interval_ms: u64 = 1000;
47
48    while let Some(arg) = args.next() {
49        match arg.as_str() {
50            "-h" | "--help" => {
51                print_help();
52                return Ok(());
53            }
54            "--json" => json = true,
55            "--journal-dump" => journal_dump = true,
56            "--journal-watch" => journal_watch = true,
57            "--start-pos" => {
58                let Some(v) = args.next() else {
59                    eprintln!("missing value for --start-pos");
60                    print_help();
61                    return Ok(());
62                };
63                start_pos = v.parse().map_err(|_| Error::InvalidArgs)?;
64            }
65            "--max-records" => {
66                let Some(v) = args.next() else {
67                    eprintln!("missing value for --max-records");
68                    print_help();
69                    return Ok(());
70                };
71                max_records = v.parse().map_err(|_| Error::InvalidArgs)?;
72            }
73            "--interval-ms" => {
74                let Some(v) = args.next() else {
75                    eprintln!("missing value for --interval-ms");
76                    print_help();
77                    return Ok(());
78                };
79                interval_ms = v.parse().map_err(|_| Error::InvalidArgs)?;
80            }
81            _ if arg.starts_with('-') => {
82                eprintln!("unknown flag: {arg}");
83                print_help();
84                return Ok(());
85            }
86            _ => {
87                if path.is_some() {
88                    eprintln!("unexpected extra arg: {arg}");
89                    print_help();
90                    return Ok(());
91                }
92                path = Some(PathBuf::from(arg));
93            }
94        }
95    }
96
97    let Some(path) = path else {
98        print_help();
99        return Ok(());
100    };
101
102    if journal_dump {
103        return dump_journal_path(&path, start_pos, max_records);
104    }
105
106    if journal_watch {
107        return watch_journal_path(&path, std::time::Duration::from_millis(interval_ms));
108    }
109
110    let report = inspect_path(&path)?;
111
112    if json {
113        println!("{}", crate::internal::json::to_json(&report));
114        return Ok(());
115    }
116
117    println!("path: {}", report.path);
118    println!("kind: {}", report.kind);
119    println!(
120        "header: magic=0x{:08x} version={} flags=0x{:04x}",
121        report.header_magic, report.header_version, report.header_flags
122    );
123    println!(
124        "caps: 0x{:08x} [{}]",
125        report.capabilities,
126        report.cap_names.join("|")
127    );
128    println!("layout_bytes: {}", report.layout_bytes);
129    println!("mapped_bytes: {}", report.mapped_bytes);
130    println!("base_size: {}", report.base_size);
131
132    match report.initialized {
133        Some(v) => println!("initialized: {v}"),
134        None => println!("initialized: n/a"),
135    }
136
137    if let Some(w) = &report.wake {
138        println!(
139            "wake: offset={} size={} present={}",
140            w.offset, w.size, w.present
141        );
142    } else {
143        println!("wake: n/a");
144    }
145
146    if !report.offsets.is_empty() {
147        println!("offsets:");
148        for (name, off) in &report.offsets {
149            println!("  {name} = {off}");
150        }
151    }
152
153    if !report.values.is_empty() {
154        println!("values:");
155        for (name, val) in &report.values {
156            println!("  {name} = {val}");
157        }
158    }
159
160    Ok(())
161}