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_disk_size = base_snap.size(SnapshotStream::Disk);
70 let overlay_len = overlay_file.metadata()?.len();
71 let final_disk_size = std::cmp::max(base_disk_size, overlay_len);
72
73 writer.begin_stream(true, final_disk_size);
75
76 let pb = ProgressBar::new(final_disk_size);
77 let bs = block_size as u64;
78 let total_blocks = final_disk_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_disk_size {
84 block_len = final_disk_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_disk_size {
94 writer.write_parent_ref(block_len as u32)?;
95 pb.inc(block_len);
96 continue;
97 }
98
99 let mut data = vec![0u8; block_len as usize];
101
102 if is_modified {
103 if block_start < base_disk_size {
104 let base_data =
105 base_snap.read_at(SnapshotStream::Disk, block_start, block_len as usize)?;
106 data[..base_data.len()].copy_from_slice(&base_data);
107 }
108
109 for ov_blk in start_ov_blk..=end_ov_blk {
110 if modified_blocks.contains(&ov_blk) {
111 let chunk_start = ov_blk * OVERLAY_BLOCK_SIZE;
112 let chunk_end =
113 std::cmp::min(chunk_start + OVERLAY_BLOCK_SIZE, final_disk_size);
114 let chunk_len = (chunk_end - chunk_start) as usize;
115 let rel_start = (chunk_start - block_start) as usize;
116
117 overlay_file.seek(SeekFrom::Start(chunk_start))?;
118 overlay_file.read_exact(&mut data[rel_start..rel_start + chunk_len])?;
119 }
120 }
121 } else if block_start < base_disk_size {
122 let base_data =
123 base_snap.read_at(SnapshotStream::Disk, block_start, block_len as usize)?;
124 data[..base_data.len()].copy_from_slice(&base_data);
125 }
126
127 writer.write_data_block(&data)?;
128 pb.inc(block_len);
129 }
130
131 writer.end_stream()?;
132
133 if let Some(mem_path) = memory_path {
135 println!("\nProcessing new memory state...");
136 let mut mem_file = File::open(&mem_path)?;
137 let mem_len = mem_file.metadata()?.len();
138
139 writer.begin_stream(false, mem_len);
140
141 let pb_mem = ProgressBar::new(mem_len);
142 let mut buf = vec![0u8; block_size as usize];
143
144 loop {
145 let mut pos = 0;
146 while pos < buf.len() {
147 match mem_file.read(&mut buf[pos..]) {
148 Ok(0) => break,
149 Ok(n) => pos += n,
150 Err(e) => return Err(e.into()),
151 }
152 }
153 if pos == 0 {
154 break;
155 }
156
157 writer.write_data_block(&buf[..pos])?;
158 pb_mem.inc(pos as u64);
159 }
160
161 writer.end_stream()?;
162 }
163
164 let parent_path = if thin {
166 Some(
167 std::fs::canonicalize(&base_path)?
168 .to_string_lossy()
169 .to_string(),
170 )
171 } else {
172 None
173 };
174
175 let meta_bytes = message.as_ref().map(|m| m.as_bytes());
176 writer.finalize(parent_path, meta_bytes)?;
177
178 if !keep_overlay {
179 println!("Cleaning up overlay files...");
180 let _ = std::fs::remove_file(&overlay_path);
181 let _ = std::fs::remove_file(&meta_path);
182 }
183
184 println!("Commit complete: {:?}", output_path);
185 Ok(())
186}