diff --git a/Cargo.lock b/Cargo.lock index 1e483593f1..0c756f628d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -295,6 +295,15 @@ dependencies = [ "wasi 0.14.7+wasi-0.2.4", ] +[[package]] +name = "humansize" +version = "2.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6cb51c9a029ddc91b07a787f1d86b53ccfa49b0e86688c946ebe8d3555685dd7" +dependencies = [ + "libm", +] + [[package]] name = "inout" version = "0.1.4" @@ -340,6 +349,12 @@ version = "0.2.176" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "58f929b4d672ea937a23a1ab494143d968337a5f47e56d0815df1e0890ddf174" +[[package]] +name = "libm" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f9fbbcab51052fe104eb5e5d351cf728d30a5be1fe14d9be8a3b097481fb97de" + [[package]] name = "libredox" version = "0.1.10" @@ -397,6 +412,12 @@ dependencies = [ "winapi", ] +[[package]] +name = "parse-size" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "487f2ccd1e17ce8c1bfab3a65c89525af41cfad4c8659021a1e9a2aacd73b89b" + [[package]] name = "pkg-config" version = "0.3.32" @@ -525,10 +546,12 @@ dependencies = [ "env_logger", "fuser", "getrandom 0.2.16", + "humansize", "libc", "libredox", "log", "lz4_flex", + "parse-size", "range-tree", "redox-path", "redox-scheme", diff --git a/Cargo.toml b/Cargo.toml index b7c462ee98..5dd698420a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -36,6 +36,12 @@ path = "src/bin/mkfs.rs" doc = false required-features = ["std"] +[[bin]] +name = "redoxfs-resize" +path = "src/bin/resize.rs" +doc = false +required-features = ["std"] + [dependencies] aes = { version = "0.8", default-features = false } argon2 = { version = "0.4", default-features = false, features = ["alloc"] } @@ -44,10 +50,12 @@ bitflags = "2" endian-num = "0.1" env_logger = { version = "0.11", optional = true } getrandom = { version = "0.2.5", optional = true } +humansize = { version = "2", optional = true } libc = "0.2" libredox = { version = "0.1.6", features = ["call"], optional = true } log = { version = "0.4.14", default-features = false, optional = true } lz4_flex = { version = "0.11", default-features = false, features = ["checked-decode"] } +parse-size = { version = "1", optional = true } range-tree = { version = "0.1", optional = true } redox-path = "0.3.0" redox-scheme = { version = "0.7.0", optional = true } @@ -63,8 +71,10 @@ std = [ "env_logger", "fuser", "getrandom", + "humansize", "libc", "libredox", + "parse-size", "range-tree", "termion", "time", diff --git a/src/bin/resize.rs b/src/bin/resize.rs new file mode 100644 index 0000000000..4ebb88b1eb --- /dev/null +++ b/src/bin/resize.rs @@ -0,0 +1,206 @@ +use std::{env, process}; + +use humansize::{format_size, BINARY, DECIMAL}; +use redoxfs::{BlockAddr, BlockMeta, Disk, DiskFile, FileSystem}; +use uuid::Uuid; + +fn resize(fs: &mut FileSystem, size_arg: String) -> Result<(), String> { + let disk_size = fs + .disk + .size() + .map_err(|err| format!("failed to read disk size: {}", err))?; + + // Find contiguous free region + //TODO: better error management + let mut last_free = None; + let mut last_end = 0; + fs.tx(|tx| { + let mut alloc_ptr = tx.header.alloc; + while !alloc_ptr.is_null() { + let alloc = tx.read_block(alloc_ptr)?; + alloc_ptr = alloc.data().prev; + for entry in alloc.data().entries.iter() { + let count = entry.count(); + if count <= 0 { + continue; + } + let end = entry.index() + count as u64; + if end > last_end { + last_free = Some(*entry); + last_end = end; + } + } + } + Ok(()) + }) + .map_err(|err| format!("failed to read alloc log: {}", err))?; + + let old_size = fs.header.size(); + let min_size = if let Some(entry) = last_free { + entry.index() * redoxfs::BLOCK_SIZE + } else { + old_size + }; + let max_size = disk_size - (fs.block * redoxfs::BLOCK_SIZE); + + let new_size = match size_arg.to_lowercase().as_str() { + "min" | "minimum" => min_size, + "" | "max" | "maximum" => max_size, + _ => match parse_size::parse_size(&size_arg) { + Ok(new_size) => { + if new_size < min_size { + return Err(format!( + "requested size {} is smaller than {} by {}", + new_size, + min_size, + min_size - new_size + )); + } + + if new_size > max_size { + return Err(format!( + "requested size {} is larger than {} by {}", + new_size, + max_size, + new_size - max_size + )); + } + + new_size + } + Err(err) => { + return Err(format!( + "failed to parse size argument {:?}: {}", + size_arg, err + )); + } + }, + }; + + println!( + "minimum size: {} ({})", + format_size(min_size, DECIMAL), + format_size(min_size, BINARY) + ); + println!( + "maximum size: {} ({})", + format_size(max_size, DECIMAL), + format_size(max_size, BINARY) + ); + println!( + "new size: {} ({})", + format_size(new_size, DECIMAL), + format_size(new_size, BINARY) + ); + + let old_blocks = old_size / redoxfs::BLOCK_SIZE; + let new_blocks = new_size / redoxfs::BLOCK_SIZE; + let (start, end, shrink) = if new_size == old_size { + println!("already requested size"); + return Ok(()); + } else if new_size < old_size { + println!("shrinking by {}", old_size - new_size); + (new_blocks, old_blocks, true) + } else { + println!("growing by {}", new_size - old_size); + (old_blocks, new_blocks, false) + }; + + // Allocate or deallocate blocks as needed + unsafe { + let allocator = fs.allocator_mut(); + for index in start..end { + if shrink { + //TODO: replace assert with error? + let addr = BlockAddr::new(index as u64, BlockMeta::default()); + assert_eq!(allocator.allocate_exact(addr), Some(addr)); + } else { + let addr = BlockAddr::new(index as u64, BlockMeta::default()); + allocator.deallocate(addr); + } + } + } + + fs.tx(|tx| { + // Update header + tx.header.size = new_size.into(); + tx.header_changed = true; + + // Sync with squash + tx.sync(true)?; + + Ok(()) + }) + .map_err(|err| format!("transaction failed: {}", err)) +} + +fn main() { + env_logger::init(); + + let mut args = env::args().skip(1); + + let disk_path = if let Some(path) = args.next() { + path + } else { + eprintln!("redoxfs-resize: no new disk image provided"); + eprintln!("redoxfs-resize NEW-DISK [SIZE]"); + process::exit(1); + }; + + let size_arg = args.next().unwrap_or_default(); + + let disk = match DiskFile::open(&disk_path) { + Ok(disk) => disk, + Err(err) => { + eprintln!( + "redoxfs-resize: failed to open disk image {}: {}", + disk_path, err + ); + process::exit(1); + } + }; + + let mut fs = match FileSystem::open(disk, None, None, true) { + Ok(fs) => fs, + Err(err) => { + eprintln!( + "redoxfs-resize: failed to open filesystem on {}: {}", + disk_path, err + ); + process::exit(1); + } + }; + + match resize(&mut fs, size_arg) { + Ok(()) => {} + Err(err) => { + eprintln!( + "redoxfs-resize: failed to resize filesystem on {}: {}", + disk_path, err + ); + process::exit(1); + } + } + + let uuid = Uuid::from_bytes(fs.header.uuid()); + let size = fs.header.size(); + let free = fs.allocator().free() * redoxfs::BLOCK_SIZE; + let used = size - free; + println!("redoxfs-resize: resized filesystem on {}", disk_path); + println!("\tuuid: {}", uuid.hyphenated()); + println!( + "\tsize: {} ({})", + format_size(size, DECIMAL), + format_size(size, BINARY) + ); + println!( + "\tused: {} ({})", + format_size(used, DECIMAL), + format_size(used, BINARY) + ); + println!( + "\tfree: {} ({})", + format_size(free, DECIMAL), + format_size(free, BINARY) + ); +} diff --git a/src/block.rs b/src/block.rs index c6e5833363..2410692f8d 100644 --- a/src/block.rs +++ b/src/block.rs @@ -21,7 +21,7 @@ impl BlockAddr { const LEVEL_MASK: u64 = 0xF; // Unsafe because this can create invalid blocks - pub(crate) unsafe fn new(index: u64, meta: BlockMeta) -> Self { + pub unsafe fn new(index: u64, meta: BlockMeta) -> Self { // Level must fit within LEVEL_MASK if meta.level.0 > Self::LEVEL_MASK as usize { panic!("block level too large"); diff --git a/src/filesystem.rs b/src/filesystem.rs index 307cb9607c..0f6f531970 100644 --- a/src/filesystem.rs +++ b/src/filesystem.rs @@ -237,6 +237,11 @@ impl FileSystem { &self.allocator } + /// Unsafe as it can corrupt the filesystem + pub unsafe fn allocator_mut(&mut self) -> &mut Allocator { + &mut self.allocator + } + /// Reset allocator to state stored on disk /// /// # Safety