Skip to content

Commit

Permalink
added free space checker
Browse files Browse the repository at this point in the history
  • Loading branch information
garikello3d committed Dec 20, 2023
1 parent 8f1b419 commit 7ff0710
Show file tree
Hide file tree
Showing 6 changed files with 94 additions and 15 deletions.
3 changes: 2 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,11 @@ version = "0.1.0"
edition = "2021"

[dependencies]
libc = "0.2.151"
liblzma = { version = "0.2.1", features = ["parallel", "static"] }
rand = { version = "0.8.5", default-features = false, features = ["std", "std_rng"] }
ring = "0.17.7"
twox-hash = "1.6.3"

[dev-dependencies]
test-case = "*"
test-case = "*"
12 changes: 6 additions & 6 deletions src/arg_opts.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ pub enum ArgModeSpecificOpts {
},
Restore {
config_path: String,
no_check_free_space: bool,
check_free_space: Option<String>,
no_check: bool,
},
Check {
Expand Down Expand Up @@ -63,7 +63,7 @@ impl ArgOpts {
("split-size", true, vec![Mode::Backup], Kind::Valued),
("compress-level", true, vec![Mode::Backup], Kind::Valued),

("no-check-free-space", false, vec![Mode::Restore], Kind::Single),
("check-free-space", false, vec![Mode::Restore], Kind::Valued),
("config", true, vec![Mode::Restore, Mode::Check], Kind::Valued),
].into_iter().map(|(c, must, m, k)|(c, OptProp{
must, modes: HashSet::from_iter(m.into_iter()), kind: k, val: None }
Expand Down Expand Up @@ -157,7 +157,7 @@ impl ArgOpts {
Mode::Restore => ArgModeSpecificOpts::Restore {
config_path: cfg.get("config").unwrap().val.clone().unwrap(),
no_check: cfg.get("no-check").unwrap().val.is_some(),
no_check_free_space: cfg.get("no-check-free-space").unwrap().val.is_some()
check_free_space: cfg.get("check-free-space").unwrap().val.clone()
},
Mode::Check => ArgModeSpecificOpts::Check {
config_path: cfg.get("config").unwrap().val.clone().unwrap(),
Expand Down Expand Up @@ -241,15 +241,15 @@ mod tests {
fn restore_opts() {
assert_eq!(
ArgOpts::from_os_args(&to_os(&vec![
"--restore", "--config", "configval", "--pass", "passval", "--buf-size", "10", "--no-check-free-space"
"--restore", "--config", "configval", "--pass", "passval", "--buf-size", "10", "--check-free-space", "/mount"
])).unwrap(),
ArgOpts{
pass: "passval".to_owned(),
buf_size: 10485760,
mode_specific_opts: ArgModeSpecificOpts::Restore {
config_path: "configval".to_owned(),
no_check: false,
no_check_free_space: true
check_free_space: Some("/mount".to_owned())
}
});
assert_eq!(
Expand All @@ -262,7 +262,7 @@ mod tests {
mode_specific_opts: ArgModeSpecificOpts::Restore {
config_path: "configval".to_owned(),
no_check: true,
no_check_free_space: false
check_free_space: None
}
});
}
Expand Down
14 changes: 8 additions & 6 deletions src/bin/bigarchiver/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -30,25 +30,27 @@ fn process_args(args: &ArgOpts) -> Result<(), String> {
if !no_check {
let cfg_path = cfg_from_pattern(&out_template);
eprintln!("verifying...");
check(None::<StdoutWriter>, &cfg_path, &args.pass, args.buf_size, false)
check(None::<StdoutWriter>, &cfg_path, &args.pass, args.buf_size, &None::<&str>)
} else {
Ok(())
}
},
ArgModeSpecificOpts::Restore { config_path, no_check, no_check_free_space } => {
ArgModeSpecificOpts::Restore { config_path, no_check, check_free_space } => {
if !no_check {
eprintln!("verifying before restore...");
check(None::<StdoutWriter>, &config_path, &args.pass, args.buf_size, false)
check(None::<StdoutWriter>, &config_path, &args.pass, args.buf_size, &None)
.map_err(|e| format!("will not restore data, integrity check error: {}", e))?;
}
eprintln!("restoring...");
check(Some(StdoutWriter{}), &config_path, &args.pass, args.buf_size, !no_check_free_space)
.map_err(|e| format!("error restoring data: {}", e))
let may_be_check = check_free_space.as_ref().map(|s| s.as_str());
check(Some(StdoutWriter{}), &config_path, &args.pass,
args.buf_size, &may_be_check)
.map_err(|e| format!("error restoring data: {}", e))
},
ArgModeSpecificOpts::Check { config_path } => {
eprintln!("verifying...");
check(None::<StdoutWriter>, &config_path, &args.pass,
args.buf_size, false)
args.buf_size, &None)
}
}
}
Expand Down
46 changes: 46 additions & 0 deletions src/free_space.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
use std::ffi::CString;
use std::mem;

pub fn get_free_space(mount_point: &str) -> Result<usize, String> {
let c_mount_point = CString::new(mount_point.as_bytes())
.map_err(|_| "null string passed as mountpoint".to_owned())?;

let mut statvfs_struct = mem::MaybeUninit::<libc::statvfs>::uninit();
unsafe {
let ret_code = libc::statvfs(c_mount_point.as_ptr(), statvfs_struct.as_mut_ptr());
if ret_code == 0 {
let statvfs_struct = statvfs_struct.assume_init();

let bsize = statvfs_struct.f_bsize as usize;
let blocks = statvfs_struct.f_blocks as usize;
let bfree = statvfs_struct.f_bfree as usize;
let bavail = statvfs_struct.f_bavail as usize;

if bsize == 0 || blocks == 0 || bfree > blocks || bavail > blocks {
return Err("inconsitent filesystem data".to_owned());
}

return Ok(bfree * bsize);
}
else {
return Err("bad mountpoint or filesystem to query".to_owned());
}
}
}

#[cfg(test)]
mod tests {
use super::*;

#[test]
fn good() {
let x = get_free_space("/tmp").unwrap();
println!("/tmp => {}", x);
assert!(x > 0);
}

#[test]
fn bad() {
assert!(get_free_space("/sdkjfsd/sdkjfdk/sdkjh").is_err());
}
}
13 changes: 12 additions & 1 deletion src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,9 @@ use splitter::Splitter;
pub mod arg_opts;
pub mod file_set;

mod free_space;
use free_space::get_free_space;

use std::time::{SystemTime, UNIX_EPOCH};
use std::io::{stdin, stdout};
use std::io::{Write, Read};
Expand Down Expand Up @@ -78,8 +81,16 @@ pub fn backup<R: Read>(
}


pub fn check<W: DataSink>(mut write_to: Option<W>, cfg_path: &str, pass: &str, buf_size_bytes: usize, _check_free_space: bool) -> Result<(), String> {
pub fn check<W: DataSink>(mut write_to: Option<W>, cfg_path: &str, pass: &str, buf_size_bytes: usize, check_free_space: &Option<&str>) -> Result<(), String> {
let stats = read_metadata::<MultiFilesReader>(cfg_path)?;

if let Some(mount_point) = check_free_space {
let all_data = stats.in_data_len.unwrap(); // SAFE because if was checked in read_metadata()
if get_free_space(mount_point)? < all_data {
return Err(format!("filesystem of '{}' won't fit {} of data to restore", mount_point, all_data));
}
}

let ref_write_to = write_to.as_mut();

let mut hash_copier = DataHasher::with_writer(ref_write_to, stats.hash_seed.unwrap());
Expand Down
21 changes: 20 additions & 1 deletion tests/backup_and_restore.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,9 @@ mod common;

use rand::RngCore;
use test_case::test_matrix;
use std::io::Write;
use std::sync::atomic::AtomicI32;
use std::fs::File;

static CNT: AtomicI32 = AtomicI32::new(0);

Expand Down Expand Up @@ -62,6 +64,23 @@ fn backup_restore_all_ok(input_size: usize, auth_size: usize, split_size: usize,
Some(src_unpacked),
&out_cfg,
"secret",
buf_size, false).unwrap();
buf_size, &None::<&str>).unwrap();

}

#[test]
fn restore_no_free_space() {
let cfg_path = "/tmp/no_free_space0.cfg";
let cfg_contents = format!("\
in_len={}\n\
in_hash=abcde\n\
hash_seed=edcba\n\
xz_len=54321\n\
nr_chunks=1\n\
chunk_len=2\n\
auth=Author Name\n\
auth_len=3", usize::MAX);
File::create(cfg_path).unwrap().write_all(cfg_contents.as_bytes()).unwrap();
let err = check(Some(SinkToVector{ incoming: Vec::new(), etalon: b"" }), cfg_path, "", 100, &Some("/tmp")).unwrap_err();
println!("err = {}", err);
}

0 comments on commit 7ff0710

Please sign in to comment.