hexz_cli/cmd/data/info.rs
1//! Inspect archive metadata and display snapshot information.
2//!
3//! This command provides a detailed inspection of Hexz snapshot files (`.st`),
4//! reading the file header and master index to display metadata about the
5//! snapshot's structure, compression, encryption status, and storage statistics.
6//!
7//! # Use Cases
8//!
9//! - **Snapshot Inspection**: Verify snapshot format version and feature flags
10//! - **Compression Analysis**: Check compression algorithm and ratio achieved
11//! - **Capacity Planning**: View original vs. compressed size for storage estimates
12//! - **Debugging**: Identify snapshot corruption or format mismatches
13//! - **Automation**: JSON output mode enables scripting and tooling integration
14//!
15//! # Workflow
16//!
17//! The command performs these steps:
18//!
19//! 1. **Header Reading**: Reads the fixed-size header (4096 bytes) from file start
20//! 2. **Index Location**: Uses `index_offset` from header to locate the master index
21//! 3. **Index Parsing**: Deserializes the master index to extract page metadata
22//! 4. **Compression Ratio**: Calculates ratio from uncompressed vs. file size
23//! 5. **Output Formatting**: Renders human-readable or JSON output
24//!
25//! # Output Format
26//!
27//! The command displays:
28//!
29//! **Header Information:**
30//! - Format version (currently v1)
31//! - Compression algorithm (LZ4 or Zstd)
32//! - Block size used for chunking
33//!
34//! **Feature Flags:**
35//! - Encryption status (encrypted or plaintext)
36//! - Disk presence (whether snapshot contains disk image)
37//! - Memory presence (whether snapshot contains memory dump)
38//! - Variable blocks (whether CDC chunking was used)
39//!
40//! **Storage Statistics:**
41//! - Original size (sum of uncompressed disk + memory)
42//! - Compressed size (total file size on disk)
43//! - Compression ratio (multiplier showing space savings)
44//!
45//! **Index Details:**
46//! - Index offset in file (byte position)
47//! - Disk pages (number of index pages for disk stream)
48//! - Memory pages (number of index pages for memory stream)
49//!
50//! # Common Usage Patterns
51//!
52//! ```bash
53//! # Inspect a snapshot with human-readable output
54//! hexz info vm-snapshot.st
55//!
56//! # Get machine-readable JSON for scripting
57//! hexz info vm-snapshot.st --json | jq .compression_ratio
58//!
59//! # Verify snapshot integrity
60//! hexz info corrupted.st # Will fail if header is malformed
61//! ```
62
63use anyhow::{Context, Result};
64use hexz_core::ops::inspect::inspect_snapshot;
65use indicatif::HumanBytes;
66use std::path::PathBuf;
67
68/// Executes the info command to display snapshot metadata.
69///
70/// Reads and parses the snapshot header and master index, then displays
71/// comprehensive metadata about the snapshot's format, compression, features,
72/// and storage statistics. Output can be formatted as human-readable text or
73/// JSON for machine consumption.
74///
75/// # Arguments
76///
77/// * `snap` - Path to the `.st` snapshot file to inspect
78/// * `json` - If true, output JSON format; otherwise, human-readable format
79///
80/// # Output Details
81///
82/// **Human-Readable Format:**
83/// Displays formatted output with sections for Features and Storage Statistics,
84/// using human-friendly byte sizes (e.g., "10.5 GB") and clearly labeled fields.
85///
86/// **JSON Format:**
87/// Outputs a single JSON object with fields:
88/// - `path`: Snapshot file path (string)
89/// - `version`: Format version number (integer)
90/// - `compression`: Compression algorithm ("Lz4" or "Zstd")
91/// - `block_size`: Block size in bytes (integer)
92/// - `encrypted`: Encryption status (boolean)
93/// - `has_disk`: Disk stream present (boolean)
94/// - `has_memory`: Memory stream present (boolean)
95/// - `variable_blocks`: CDC chunking enabled (boolean)
96/// - `original_size`: Uncompressed size in bytes (integer)
97/// - `compressed_size`: File size in bytes (integer)
98/// - `compression_ratio`: Compression multiplier (float)
99/// - `index_offset`: Master index byte offset (integer)
100/// - `disk_pages`: Number of disk index pages (integer)
101/// - `memory_pages`: Number of memory index pages (integer)
102///
103/// # Errors
104///
105/// Returns an error if:
106/// - The snapshot file cannot be opened (file not found, permission denied)
107/// - The header cannot be read (file too small, I/O error)
108/// - The header format is invalid (corrupted file, wrong format)
109/// - The master index cannot be read (corrupted index, truncated file)
110/// - The master index format is invalid (version mismatch, corrupted data)
111///
112/// # Examples
113///
114/// ```no_run
115/// use std::path::PathBuf;
116/// use hexz_cli::cmd::data::info;
117///
118/// // Display human-readable snapshot information
119/// info::run(PathBuf::from("snapshot.hxz"), false)?;
120///
121/// // Output JSON for automated processing
122/// info::run(PathBuf::from("snapshot.hxz"), true)?;
123/// # Ok::<(), anyhow::Error>(())
124/// ```
125pub fn run(snap: PathBuf, json: bool) -> Result<()> {
126 let info = inspect_snapshot(&snap).context("Failed to inspect snapshot")?;
127
128 let total_uncompressed = info.total_uncompressed();
129 let ratio = info.compression_ratio();
130
131 if json {
132 println!("{{");
133 println!(" \"path\": {:?},", snap);
134 println!(" \"version\": {},", info.version);
135 println!(" \"compression\": {:?},", info.compression);
136 println!(" \"block_size\": {},", info.block_size);
137 println!(" \"encrypted\": {},", info.encrypted);
138 println!(" \"has_disk\": {},", info.has_disk);
139 println!(" \"has_memory\": {},", info.has_memory);
140 println!(" \"variable_blocks\": {},", info.variable_blocks);
141 println!(" \"original_size\": {},", total_uncompressed);
142 println!(" \"compressed_size\": {},", info.file_size);
143 println!(" \"compression_ratio\": {:.2},", ratio);
144 println!(" \"index_offset\": {},", info.index_offset);
145 println!(" \"disk_pages\": {},", info.disk_pages);
146 println!(" \"memory_pages\": {}", info.memory_pages);
147 println!("}}");
148 } else {
149 println!("Snapshot: {:?}", snap);
150 println!("Format Version: {}", info.version);
151 println!("Compression: {:?}", info.compression);
152 println!("Block Size: {}", info.block_size);
153
154 println!("\n--- Features ---");
155 println!(
156 "Encrypted: {}",
157 if info.encrypted { "Yes" } else { "No" }
158 );
159 println!(
160 "Has Disk: {}",
161 if info.has_disk { "Yes" } else { "No" }
162 );
163 println!(
164 "Has Memory: {}",
165 if info.has_memory { "Yes" } else { "No" }
166 );
167 println!(
168 "Variable Blks: {}",
169 if info.variable_blocks {
170 "Yes (CDC)"
171 } else {
172 "No"
173 }
174 );
175
176 println!("\n--- Storage Statistics ---");
177 println!("Original Size: {}", HumanBytes(total_uncompressed));
178 println!("Compressed: {}", HumanBytes(info.file_size));
179 println!("Ratio: {:.2}x", ratio);
180
181 println!("\n--- Index Details ---");
182 println!("Index Offset: {}", info.index_offset);
183 println!("Disk Pages: {}", info.disk_pages);
184 println!("Memory Pages: {}", info.memory_pages);
185 }
186
187 Ok(())
188}