forked from brl/citadel-tools
127 lines
3.9 KiB
Rust
127 lines
3.9 KiB
Rust
use std::fs::File;
|
|
use std::io::{Read,Seek,SeekFrom};
|
|
use std::path::Path;
|
|
|
|
use byteorder::{ByteOrder,LittleEndian};
|
|
|
|
use crate::{RealmFS,Result};
|
|
|
|
const BLOCK_SIZE: usize = 4096;
|
|
const BLOCKS_PER_MEG: usize = (1024 * 1024) / BLOCK_SIZE;
|
|
const BLOCKS_PER_GIG: usize = 1024 * BLOCKS_PER_MEG;
|
|
|
|
// If less than 1gb remaining space
|
|
const AUTO_RESIZE_MINIMUM_FREE: ResizeSize = ResizeSize(BLOCKS_PER_GIG);
|
|
// ... add 4gb to size of image
|
|
const AUTO_RESIZE_INCREASE_SIZE: ResizeSize = ResizeSize(4 * BLOCKS_PER_GIG);
|
|
|
|
|
|
#[derive(Copy,Clone)]
|
|
pub struct ResizeSize(usize);
|
|
|
|
impl ResizeSize {
|
|
|
|
pub fn gigs(n: usize) -> Self {
|
|
ResizeSize(BLOCKS_PER_GIG * n)
|
|
}
|
|
|
|
pub fn megs(n: usize) -> Self {
|
|
ResizeSize(BLOCKS_PER_MEG * n)
|
|
}
|
|
|
|
pub fn blocks(n: usize) -> Self {
|
|
ResizeSize(n)
|
|
}
|
|
|
|
pub fn nblocks(&self) -> usize {
|
|
self.0
|
|
}
|
|
|
|
pub fn size_in_gb(&self) -> usize {
|
|
self.0 / BLOCKS_PER_GIG
|
|
}
|
|
|
|
pub fn size_in_mb(&self) -> usize {
|
|
self.0 / BLOCKS_PER_MEG
|
|
}
|
|
|
|
/// If the RealmFS has less than `AUTO_RESIZE_MINIMUM_FREE` blocks free then choose a new
|
|
/// size to resize the filesystem to and return it. Otherwise, return `None`
|
|
pub fn auto_resize_size(realmfs: &RealmFS) -> Option<ResizeSize> {
|
|
let sb = match Superblock::load(realmfs.path(), 4096) {
|
|
Ok(sb) => sb,
|
|
Err(e) => {
|
|
warn!("Error reading superblock from {}: {}", realmfs.path().display(), e);
|
|
return None;
|
|
},
|
|
};
|
|
|
|
let free_blocks = sb.free_block_count() as usize;
|
|
if free_blocks >= AUTO_RESIZE_MINIMUM_FREE.nblocks() {
|
|
return None;
|
|
}
|
|
|
|
let metainfo_nblocks = realmfs.metainfo().nblocks();
|
|
|
|
if metainfo_nblocks >= AUTO_RESIZE_INCREASE_SIZE.nblocks() {
|
|
return Some(ResizeSize::blocks(metainfo_nblocks + AUTO_RESIZE_INCREASE_SIZE.nblocks()))
|
|
}
|
|
|
|
// If current size is under 4GB (AUTO_RESIZE_INCREASE_SIZE) and raising size to 4GB will create more than the
|
|
// minimum free space (1GB) then just do that.
|
|
if free_blocks + (AUTO_RESIZE_INCREASE_SIZE.nblocks() - metainfo_nblocks) >= AUTO_RESIZE_MINIMUM_FREE.nblocks() {
|
|
Some(AUTO_RESIZE_INCREASE_SIZE)
|
|
} else {
|
|
// Otherwise for original size under 4GB, since raising to 4GB is not enough,
|
|
// raise size to 8GB
|
|
Some(ResizeSize::blocks(AUTO_RESIZE_INCREASE_SIZE.nblocks() * 2))
|
|
}
|
|
}
|
|
}
|
|
|
|
const SUPERBLOCK_SIZE: usize = 1024;
|
|
|
|
/// An EXT4 superblock structure.
|
|
///
|
|
/// A class for reading the first superblock from an EXT4 filesystem
|
|
/// and parsing the Free Block Count field. No other fields are parsed
|
|
/// since this is the only information needed for the resize operation.
|
|
///
|
|
pub struct Superblock([u8; SUPERBLOCK_SIZE]);
|
|
|
|
impl Superblock {
|
|
fn new() -> Self {
|
|
Superblock([0u8; SUPERBLOCK_SIZE])
|
|
}
|
|
|
|
pub fn load(path: impl AsRef<Path>, offset: u64) -> Result<Self> {
|
|
let path = path.as_ref();
|
|
let mut sb = Self::new();
|
|
let mut file = File::open(path)
|
|
.map_err(context!("failed to open image file {:?}", path))?;
|
|
file.seek(SeekFrom::Start(1024 + offset))
|
|
.map_err(context!("failed to seek to offset {} of image file {:?}", 1024 + offset, path))?;
|
|
file.read_exact(&mut sb.0)
|
|
.map_err(context!("error reading superblock from image file {:?}", path))?;
|
|
Ok(sb)
|
|
}
|
|
|
|
pub fn free_block_count(&self) -> u64 {
|
|
self.split_u64(0x0C, 0x158)
|
|
}
|
|
|
|
fn u32(&self, offset: usize) -> u32 {
|
|
LittleEndian::read_u32(self.at(offset))
|
|
}
|
|
|
|
fn split_u64(&self, offset_lo: usize, offset_hi: usize) -> u64 {
|
|
let lo = u64::from(self.u32(offset_lo));
|
|
let hi = u64::from(self.u32(offset_hi));
|
|
(hi << 32) | lo
|
|
}
|
|
|
|
fn at(&self, offset: usize) -> &[u8] {
|
|
&self.0[offset..]
|
|
}
|
|
}
|