Add resizing support
This commit is contained in:
@@ -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<D: Disk>(fs: &mut FileSystem<D>, 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)
|
||||
);
|
||||
}
|
||||
+1
-1
@@ -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");
|
||||
|
||||
@@ -237,6 +237,11 @@ impl<D: Disk> FileSystem<D> {
|
||||
&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
|
||||
|
||||
Reference in New Issue
Block a user