1#![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}