hexz_cli/cmd/vm/
commit.rs1use anyhow::Result;
9use hexz_common::constants::{META_ENTRY_SIZE, OVERLAY_BLOCK_SIZE};
10use hexz_core::File as HexzFile;
11use hexz_core::algo::compression::create_compressor_from_str;
12use hexz_core::api::file::SnapshotStream;
13use hexz_core::ops::snapshot_writer::SnapshotWriter;
14use hexz_core::store::local::FileBackend;
15use indicatif::ProgressBar;
16use std::collections::HashSet;
17use std::fs::File;
18use std::io::{Read, Seek, SeekFrom};
19use std::path::PathBuf;
20use std::sync::Arc;
21
22#[allow(clippy::too_many_arguments)]
24pub fn run(
25 base_path: PathBuf,
26 overlay_path: PathBuf,
27 memory_path: Option<PathBuf>,
28 output_path: PathBuf,
29 algo: String,
30 block_size: u32,
31 keep_overlay: bool,
32 message: Option<String>,
33 thin: bool,
34) -> Result<()> {
35 println!(
36 "Committing changes from {:?} to {:?} (Thin: {})",
37 overlay_path, output_path, thin
38 );
39
40 let backend = Arc::new(FileBackend::new(&base_path)?);
41 let base_snap = HexzFile::open(backend, None)?;
42
43 let meta_path = overlay_path.with_extension("meta");
44 let mut modified_blocks = HashSet::new();
45
46 if meta_path.exists() {
47 let mut f = File::open(&meta_path)?;
48 let mut buf = [0u8; META_ENTRY_SIZE];
49 loop {
50 match f.read_exact(&mut buf) {
51 Ok(_) => {
52 modified_blocks.insert(u64::from_le_bytes(buf));
53 }
54 Err(ref e) if e.kind() == std::io::ErrorKind::UnexpectedEof => break,
55 Err(e) => return Err(e.into()),
56 }
57 }
58 }
59
60 let mut overlay_file = File::open(&overlay_path)?;
61
62 let (write_compressor, compression_type) = create_compressor_from_str(&algo, None, None)?;
63
64 let mut writer = SnapshotWriter::builder(&output_path, write_compressor, compression_type)
65 .block_size(block_size)
66 .variable_blocks(false)
67 .build()?;
68
69 let base_primary_size = base_snap.size(SnapshotStream::Primary);
70 let overlay_len = overlay_file.metadata()?.len();
71 let final_primary_size = std::cmp::max(base_primary_size, overlay_len);
72
73 writer.begin_stream(true, final_primary_size);
75
76 let pb = ProgressBar::new(final_primary_size);
77 let bs = block_size as u64;
78 let total_blocks = final_primary_size.div_ceil(bs);
79
80 for i in 0..total_blocks {
81 let block_start = i * bs;
82 let mut block_len = bs;
83 if block_start + block_len > final_primary_size {
84 block_len = final_primary_size - block_start;
85 }
86
87 let start_ov_blk = block_start / OVERLAY_BLOCK_SIZE;
88 let end_ov_blk = (block_start + block_len - 1) / OVERLAY_BLOCK_SIZE;
89 let is_modified =
90 (start_ov_blk..=end_ov_blk).any(|ov_blk| modified_blocks.contains(&ov_blk));
91
92 if thin && !is_modified && block_start < base_primary_size {
94 let hash = base_snap
95 .get_block_info(SnapshotStream::Primary, block_start)?
96 .map(|(_, info)| info.hash)
97 .unwrap_or([0u8; 32]);
98
99 writer.write_parent_ref(&hash, block_len as u32)?;
100 pb.inc(block_len);
101 continue;
102 }
103
104 let mut data = vec![0u8; block_len as usize];
106
107 if is_modified {
108 if block_start < base_primary_size {
109 let base_data =
110 base_snap.read_at(SnapshotStream::Primary, block_start, block_len as usize)?;
111 data[..base_data.len()].copy_from_slice(&base_data);
112 }
113
114 for ov_blk in start_ov_blk..=end_ov_blk {
115 if modified_blocks.contains(&ov_blk) {
116 let chunk_start = ov_blk * OVERLAY_BLOCK_SIZE;
117 let chunk_end =
118 std::cmp::min(chunk_start + OVERLAY_BLOCK_SIZE, final_primary_size);
119 let chunk_len = (chunk_end - chunk_start) as usize;
120 let rel_start = (chunk_start - block_start) as usize;
121
122 overlay_file.seek(SeekFrom::Start(chunk_start))?;
123 overlay_file.read_exact(&mut data[rel_start..rel_start + chunk_len])?;
124 }
125 }
126 } else if block_start < base_primary_size {
127 let base_data =
128 base_snap.read_at(SnapshotStream::Primary, block_start, block_len as usize)?;
129 data[..base_data.len()].copy_from_slice(&base_data);
130 }
131
132 writer.write_data_block(&data)?;
133 pb.inc(block_len);
134 }
135
136 writer.end_stream()?;
137
138 if let Some(mem_path) = memory_path {
140 println!("\nProcessing new memory state...");
141 let mut mem_file = File::open(&mem_path)?;
142 let mem_len = mem_file.metadata()?.len();
143
144 writer.begin_stream(false, mem_len);
145
146 let pb_mem = ProgressBar::new(mem_len);
147 let mut buf = vec![0u8; block_size as usize];
148
149 loop {
150 let mut pos = 0;
151 while pos < buf.len() {
152 match mem_file.read(&mut buf[pos..]) {
153 Ok(0) => break,
154 Ok(n) => pos += n,
155 Err(e) => return Err(e.into()),
156 }
157 }
158 if pos == 0 {
159 break;
160 }
161
162 writer.write_data_block(&buf[..pos])?;
163 pb_mem.inc(pos as u64);
164 }
165
166 writer.end_stream()?;
167 }
168
169 let parent_path = if thin {
171 Some(
172 std::fs::canonicalize(&base_path)?
173 .to_string_lossy()
174 .to_string(),
175 )
176 } else {
177 None
178 };
179
180 let meta_bytes = message.as_ref().map(|m| m.as_bytes());
181 writer.finalize(parent_path, meta_bytes)?;
182
183 if !keep_overlay {
184 println!("Cleaning up overlay files...");
185 let _ = std::fs::remove_file(&overlay_path);
186 let _ = std::fs::remove_file(&meta_path);
187 }
188
189 println!("Commit complete: {:?}", output_path);
190 Ok(())
191}