Skip to content

Commit 4d225df

Browse files
committed
Verify if data was copied without errors
1 parent 18a6fe6 commit 4d225df

File tree

5 files changed

+242
-103
lines changed

5 files changed

+242
-103
lines changed

Cargo.lock

+2-1
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

+2-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[package]
22
name = "imge"
3-
version = "0.4.0"
3+
version = "0.5.0"
44
edition = "2021"
55
description = "Write disk images to physical drive or vice versa."
66
readme = "README.md"
@@ -15,6 +15,7 @@ crossterm = "0.28"
1515
derivative = "2.2"
1616
drives = "0.6"
1717
libarchive3-sys = "0.1"
18+
libc = "0.2"
1819
mime = "0.3"
1920
mime_guess = "2.0"
2021
num-format = { version = "0.4", features = ["with-system-locale"] }

src/imge.rs

+155-81
Original file line numberDiff line numberDiff line change
@@ -4,14 +4,18 @@
44

55
use libarchive3_sys::ffi::*;
66
use mime::Mime;
7+
use std::alloc::{alloc, Layout};
78
use std::ffi::{c_void, CString, OsString};
8-
use std::fs::File;
9-
use std::io;
9+
use std::fs::{File, OpenOptions};
1010
use std::io::prelude::*;
11+
use std::io::{self, Read};
1112
use std::os::unix::ffi::OsStrExt;
13+
use std::os::unix::fs::OpenOptionsExt;
1214
use std::sync::{Arc, Mutex};
1315
use std::time::Instant;
1416

17+
const BLOCK_SIZE: usize = 1024 * 1024;
18+
1519
pub struct Drive {
1620
pub name: OsString,
1721
pub model: String,
@@ -52,8 +56,8 @@ pub fn list(all_drives: bool) -> Vec<Drive> {
5256

5357
#[derive(Default)]
5458
pub struct Progress {
55-
pub copied: u64,
5659
pub size: u64,
60+
pub done: u64,
5761
pub secs: u64,
5862
pub finished: bool,
5963
}
@@ -63,7 +67,7 @@ impl Progress {
6367
if self.size == 0 {
6468
0.0
6569
} else {
66-
self.copied as f64 / self.size as f64
70+
self.done as f64 / self.size as f64
6771
}
6872
}
6973
}
@@ -73,90 +77,108 @@ pub struct Path {
7377
pub size: Option<u64>,
7478
}
7579

76-
pub fn copy(src: &Path, dest: &Path, from_drive: bool, image_mime_type: Mime,
77-
progress_mutex: &Arc<Mutex<Progress>>) -> io::Result<()> {
80+
fn libarchive_open_for_reading(path: &Path)
81+
-> io::Result<(*mut Struct_archive, *mut Struct_archive_entry)> {
7882

79-
if !from_drive {
80-
if let (Some(ssize), Some(dsize)) = (src.size, dest.size) {
81-
if ssize > dsize {
82-
return Err(io::Error::other("File too large (os error 27)"));
83-
}
83+
let filename = CString::new(path.path.as_bytes()).unwrap();
84+
85+
unsafe {
86+
let a = archive_read_new();
87+
archive_read_support_filter_all(a);
88+
archive_read_support_format_raw(a);
89+
90+
let rc = archive_read_open_filename(a, filename.as_ptr(), BLOCK_SIZE);
91+
if rc != ARCHIVE_OK {
92+
return Err(io::Error::other("Cannot open file for reading"));
93+
}
94+
95+
let ae = archive_entry_new2(a);
96+
let ae_ptr = ae as *mut *mut Struct_archive_entry;
97+
let rc = archive_read_next_header(a, ae_ptr);
98+
if rc != ARCHIVE_OK {
99+
return Err(io::Error::other("Cannot read file header"));
84100
}
101+
102+
Ok((a, ae))
85103
}
104+
}
86105

87-
let mut srcfile = File::open(&src.path)?;
106+
fn libarchive_open_for_writing(path: &Path, image_mime_type: Mime)
107+
-> io::Result<(*mut Struct_archive, *mut Struct_archive_entry)> {
88108

89-
let a = unsafe {
90-
match from_drive {
91-
false => {
92-
let a = archive_read_new();
93-
archive_read_support_filter_all(a);
94-
archive_read_support_format_raw(a);
95-
a
96-
},
97-
true => {
98-
let a = archive_write_new();
99-
let filter = match image_mime_type.essence_str() {
100-
"application/gzip" => 1,
101-
"application/x-bzip2" => 2,
102-
"application/x-xz" => 6,
103-
"application/zstd" => 14,
104-
_ => 0,
105-
};
106-
archive_write_add_filter(a, filter);
107-
archive_write_set_format(a, 0x90000); // RAW
108-
a
109-
},
110-
}
111-
};
109+
let filename = CString::new(path.path.as_bytes()).unwrap();
112110

113-
let ae = if !from_drive {
114-
unsafe {
115-
let srcfilename = CString::new(src.path.as_bytes()).unwrap();
116-
let rc = archive_read_open_filename(a, srcfilename.as_ptr(), 1024 * 1024);
117-
if rc != ARCHIVE_OK {
118-
return Err(io::Error::other("Cannot open file for reading"));
119-
}
111+
unsafe {
112+
let a = archive_write_new();
113+
let filter = match image_mime_type.essence_str() {
114+
"application/gzip" => 1,
115+
"application/x-bzip2" => 2,
116+
"application/x-xz" => 6,
117+
"application/zstd" => 14,
118+
_ => 0,
119+
};
120+
archive_write_add_filter(a, filter);
121+
archive_write_set_format(a, 0x90000); // RAW
120122

121-
let ae = archive_entry_new2(a);
122-
let ae_ptr = ae as *mut *mut Struct_archive_entry;
123-
let rc = archive_read_next_header(a, ae_ptr);
124-
if rc != ARCHIVE_OK {
125-
return Err(io::Error::other("Cannot read file header"));
126-
}
127-
ae
123+
let rc = archive_write_open_filename(a, filename.as_ptr());
124+
if rc != ARCHIVE_OK {
125+
return Err(io::Error::other("Cannot open file for writing"));
128126
}
129-
} else {
130-
unsafe {
131-
let destfilename = CString::new(dest.path.as_bytes()).unwrap();
132-
let rc = archive_write_open_filename(a, destfilename.as_ptr());
133-
if rc != ARCHIVE_OK {
134-
return Err(io::Error::other("Cannot open file for writing"));
135-
}
136127

137-
let ae = archive_entry_new2(a);
138-
archive_entry_set_filetype(ae, AE_IFREG);
139-
let rc = archive_write_header(a, ae);
140-
if rc != ARCHIVE_OK {
141-
return Err(io::Error::other("Cannot write file header"));
142-
}
143-
ae
128+
let ae = archive_entry_new2(a);
129+
archive_entry_set_filetype(ae, AE_IFREG);
130+
let rc = archive_write_header(a, ae);
131+
if rc != ARCHIVE_OK {
132+
return Err(io::Error::other("Cannot write file header"));
133+
}
134+
135+
Ok((a, ae))
136+
}
137+
}
138+
139+
fn libarchive_close_for_reading(a: *mut Struct_archive) {
140+
unsafe {
141+
archive_read_close(a);
142+
archive_read_free(a);
143+
}
144+
}
145+
146+
fn libarchive_close_for_writing(a: *mut Struct_archive, ae: *mut Struct_archive_entry) {
147+
unsafe {
148+
archive_entry_free(ae);
149+
archive_write_close(a);
150+
archive_write_free(a);
151+
}
152+
}
153+
154+
pub fn copy(src: &Path, dest: &Path, from_drive: bool, image_mime_type: Mime,
155+
progress_mutex: &Arc<Mutex<Progress>>) -> io::Result<()> {
156+
157+
if let (Some(ssize), Some(dsize)) = (src.size, dest.size) {
158+
if !from_drive && ssize > dsize {
159+
return Err(io::Error::other("File too large (os error 27)"));
144160
}
161+
}
162+
163+
let mut srcfile = File::open(&src.path)?;
164+
let mut destfile = OpenOptions::new()
165+
.create(true).write(true).truncate(true)
166+
.custom_flags(libc::O_DSYNC).open(&dest.path)?;
167+
168+
let (a, ae) = match from_drive {
169+
false => libarchive_open_for_reading(&src)?,
170+
true => libarchive_open_for_writing(&dest, image_mime_type.clone())?,
145171
};
146172

147-
let mut destfile = File::create(&dest.path)?;
148-
let mut buffer = [0; 1024 * 1024];
173+
let mut buffer = [0; BLOCK_SIZE];
149174
let buffer_ptr = buffer.as_mut_ptr() as *mut c_void;
150-
let timer = Instant::now();
151175

152-
let mut progress = progress_mutex.lock().unwrap();
153-
progress.size = src.size.unwrap_or_default();
154-
drop(progress);
176+
let timer = Instant::now();
155177

156178
loop {
157179
let len = match from_drive {
158180
false => unsafe {
159-
archive_read_data(a, buffer_ptr, 1024 * 1024)
181+
archive_read_data(a, buffer_ptr, BLOCK_SIZE)
160182
},
161183
true => srcfile.read(&mut buffer)? as isize,
162184
};
@@ -170,26 +192,78 @@ pub fn copy(src: &Path, dest: &Path, from_drive: bool, image_mime_type: Mime,
170192
}
171193
} else {
172194
destfile.write_all(&buffer[..(len as usize)])?;
173-
destfile.sync_data()?;
174195
}
175196

176197
let mut progress = progress_mutex.lock().unwrap();
177-
progress.copied += len as u64;
198+
progress.done += len as u64;
178199
}
179200

180-
unsafe {
181-
if !from_drive {
182-
archive_read_close(a);
183-
archive_read_free(a);
184-
} else {
185-
archive_entry_free(ae);
186-
archive_write_close(a);
187-
archive_write_free(a);
201+
match from_drive {
202+
false => libarchive_close_for_reading(a),
203+
true => libarchive_close_for_writing(a, ae),
204+
};
205+
206+
let mut progress = progress_mutex.lock().unwrap();
207+
progress.secs = timer.elapsed().as_secs();
208+
progress.finished = true;
209+
210+
Ok(())
211+
}
212+
213+
pub fn verify(src: &Path, dest: &Path, from_drive: bool,
214+
progress_mutex: &Arc<Mutex<Progress>>) -> io::Result<()> {
215+
216+
let mut srcfile = File::open(&src.path)?;
217+
let mut destfile = OpenOptions::new().read(true)
218+
.custom_flags(libc::O_DIRECT).open(&dest.path)?;
219+
220+
let (a, _ae) = match from_drive {
221+
false => libarchive_open_for_reading(&src)?,
222+
true => libarchive_open_for_reading(&dest)?,
223+
};
224+
225+
226+
let mut srcbuffer = [0; BLOCK_SIZE];
227+
let srcbuffer_ptr = srcbuffer.as_mut_ptr() as *mut c_void;
228+
let destbuffer_ptr = unsafe {
229+
alloc(Layout::from_size_align(BLOCK_SIZE, BLOCK_SIZE).unwrap())
230+
};
231+
let mut destbuffer = unsafe {
232+
std::slice::from_raw_parts_mut(destbuffer_ptr, BLOCK_SIZE)
233+
};
234+
235+
let timer = Instant::now();
236+
237+
loop {
238+
let len = match from_drive {
239+
false => unsafe {
240+
archive_read_data(a, srcbuffer_ptr, BLOCK_SIZE)
241+
},
242+
true => srcfile.read(&mut srcbuffer)? as isize,
243+
};
244+
if len <= 0 {
245+
break;
188246
}
247+
248+
match from_drive {
249+
false => destfile.read(&mut destbuffer)? as isize,
250+
true => unsafe {
251+
archive_read_data(a, destbuffer_ptr as *mut c_void, BLOCK_SIZE)
252+
},
253+
};
254+
255+
if &srcbuffer[..len as usize] != &destbuffer[..len as usize] {
256+
return Err(io::Error::other("Verification failed"));
257+
}
258+
259+
let mut progress = progress_mutex.lock().unwrap();
260+
progress.done += len as u64;
189261
}
190262

263+
libarchive_close_for_reading(a);
264+
191265
let mut progress = progress_mutex.lock().unwrap();
192-
progress.secs = timer.elapsed().as_secs();
266+
progress.secs += timer.elapsed().as_secs();
193267
progress.finished = true;
194268

195269
Ok(())

src/main.rs

+4
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,10 @@ struct Args {
2828
#[argh(switch, short='f')]
2929
from_drive: bool,
3030

31+
/// verify if data was copied correctly
32+
#[argh(switch, short='v')]
33+
verify: bool,
34+
3135
/// path to image
3236
#[argh(positional)]
3337
image: OsString,

0 commit comments

Comments
 (0)