2019-04-02 15:09:41 -04:00
|
|
|
use std::fs::{File,OpenOptions};
|
|
|
|
use std::io::{Read,Seek,SeekFrom};
|
|
|
|
use std::path::Path;
|
|
|
|
|
|
|
|
use byteorder::{ByteOrder,LittleEndian};
|
|
|
|
|
|
|
|
use crate::{RealmFS,Result,LoopDevice};
|
|
|
|
|
|
|
|
const BLOCK_SIZE: usize = 4096;
|
|
|
|
const BLOCKS_PER_MEG: usize = (1024 * 1024) / BLOCK_SIZE;
|
|
|
|
const BLOCKS_PER_GIG: usize = 1024 * BLOCKS_PER_MEG;
|
|
|
|
|
2019-05-14 11:07:32 -04:00
|
|
|
const E2FSCK: &str = "e2fsck";
|
2019-04-02 15:09:41 -04:00
|
|
|
const RESIZE2FS: &str = "resize2fs";
|
|
|
|
|
|
|
|
// If less than 1gb remaining space
|
2019-04-03 16:05:09 -04:00
|
|
|
const AUTO_RESIZE_MINIMUM_FREE: ResizeSize = ResizeSize(BLOCKS_PER_GIG);
|
2019-04-02 15:09:41 -04:00
|
|
|
// ... add 4gb to size of image
|
|
|
|
const AUTO_RESIZE_INCREASE_SIZE: ResizeSize = ResizeSize(4 * BLOCKS_PER_GIG);
|
|
|
|
|
|
|
|
pub struct ImageResizer<'a> {
|
|
|
|
image: &'a RealmFS,
|
|
|
|
}
|
|
|
|
|
2019-04-03 16:05:09 -04:00
|
|
|
#[derive(Copy,Clone)]
|
2019-04-02 15:09:41 -04:00
|
|
|
pub struct ResizeSize(usize);
|
|
|
|
|
|
|
|
impl ResizeSize {
|
|
|
|
|
2019-04-03 16:05:09 -04:00
|
|
|
pub fn gigs(n: usize) -> Self {
|
2019-04-02 15:09:41 -04:00
|
|
|
ResizeSize(BLOCKS_PER_GIG * n)
|
|
|
|
|
|
|
|
}
|
2019-04-03 16:05:09 -04:00
|
|
|
pub fn megs(n: usize) -> Self {
|
2019-04-02 15:09:41 -04:00
|
|
|
ResizeSize(BLOCKS_PER_MEG * n)
|
|
|
|
}
|
|
|
|
|
2019-04-03 16:05:09 -04:00
|
|
|
pub fn blocks(n: usize) -> Self {
|
2019-04-02 15:09:41 -04:00
|
|
|
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
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl <'a> ImageResizer<'a> {
|
|
|
|
|
|
|
|
pub fn new(image: &'a RealmFS) -> ImageResizer<'a> {
|
|
|
|
ImageResizer { image }
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn grow_to(&mut self, size: ResizeSize) -> Result<()> {
|
|
|
|
let target_nblocks = size.nblocks();
|
|
|
|
let current_nblocks = self.image.metainfo_nblocks();
|
|
|
|
if current_nblocks >= target_nblocks {
|
|
|
|
info!("RealmFS image is already larger than requested size, doing nothing");
|
|
|
|
} else {
|
|
|
|
let size = ResizeSize::blocks(target_nblocks - current_nblocks);
|
|
|
|
self.grow_by(size)?;
|
|
|
|
}
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn grow_by(&mut self, size: ResizeSize) -> Result<()> {
|
|
|
|
let nblocks = size.nblocks();
|
|
|
|
let new_nblocks = self.image.metainfo_nblocks() + nblocks;
|
|
|
|
if self.image.is_sealed() {
|
|
|
|
bail!("Cannot resize sealed image '{}'. unseal first", self.image.name());
|
|
|
|
}
|
|
|
|
self.resize(new_nblocks)
|
|
|
|
}
|
|
|
|
|
|
|
|
fn resize(&self, new_nblocks: usize) -> Result<()> {
|
|
|
|
if new_nblocks < self.image.metainfo_nblocks() {
|
|
|
|
bail!("Cannot shrink image")
|
|
|
|
}
|
|
|
|
|
|
|
|
if (new_nblocks - self.image.metainfo_nblocks()) > ResizeSize::gigs(8).nblocks() {
|
|
|
|
bail!("Can only increase size of RealmFS image by a maximum of 8gb at one time");
|
|
|
|
}
|
|
|
|
|
|
|
|
ImageResizer::resize_image_file(self.image.path(), new_nblocks)?;
|
|
|
|
|
|
|
|
if let Some(open_loop) = self.notify_open_loops()? {
|
2019-05-14 11:07:32 -04:00
|
|
|
info!("Running e2fsck {:?}", open_loop);
|
|
|
|
cmd!(E2FSCK,"{} {} {}","-f","-p",open_loop.device().display())?;
|
2019-04-02 15:09:41 -04:00
|
|
|
info!("Running resize2fs {:?}", open_loop);
|
|
|
|
cmd!(RESIZE2FS, "{}", open_loop.device().display())?;
|
|
|
|
} else {
|
|
|
|
LoopDevice::with_loop(self.image.path(), Some(4096), false, |loopdev| {
|
2019-05-14 11:07:32 -04:00
|
|
|
info!("Running e2fsck {:?}", loopdev);
|
|
|
|
cmd!(E2FSCK,"{} {} {}","-f","-p",loopdev.device().display())?;
|
2019-04-02 15:09:41 -04:00
|
|
|
info!("Running resize2fs {:?}", loopdev);
|
|
|
|
cmd!(RESIZE2FS, "{}", loopdev.device().display())?;
|
|
|
|
Ok(())
|
|
|
|
})?;
|
|
|
|
}
|
|
|
|
let owner = self.image.metainfo().realmfs_owner().map(|s| s.to_owned());
|
|
|
|
self.image.update_unsealed_metainfo(self.image.name(), new_nblocks - 1, owner)?;
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
|
|
|
fn resize_image_file(file: &Path, nblocks: usize) -> Result<()> {
|
|
|
|
let len = nblocks * BLOCK_SIZE;
|
|
|
|
info!("Resizing image file to {}", len);
|
|
|
|
OpenOptions::new()
|
|
|
|
.write(true)
|
|
|
|
.open(file)?
|
|
|
|
.set_len(len as u64)?;
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
|
|
|
fn notify_open_loops(&self) -> Result<Option<LoopDevice>> {
|
|
|
|
let mut open_loop = None;
|
|
|
|
for loopdev in LoopDevice::find_devices_for(self.image.path())? {
|
|
|
|
loopdev.resize()
|
|
|
|
.unwrap_or_else(|err| warn!("Error running losetup -c {:?}: {}", loopdev, err));
|
|
|
|
open_loop = Some(loopdev);
|
|
|
|
}
|
|
|
|
Ok(open_loop)
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// If the RealmFS needs to be resized to a larger size, returns the
|
|
|
|
/// recommended size. Pass this value to `ImageResizer.grow_to()` to
|
|
|
|
/// complete the resize.
|
|
|
|
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;
|
|
|
|
},
|
|
|
|
};
|
|
|
|
|
|
|
|
sb.free_block_count();
|
|
|
|
let free_blocks = sb.free_block_count() as usize;
|
|
|
|
if free_blocks < AUTO_RESIZE_MINIMUM_FREE.nblocks() {
|
2019-05-14 11:07:32 -04:00
|
|
|
let increase_multiple = realmfs.metainfo_nblocks() / AUTO_RESIZE_INCREASE_SIZE.nblocks();
|
|
|
|
let grow_size = (increase_multiple + 1) * AUTO_RESIZE_INCREASE_SIZE.nblocks();
|
|
|
|
let mask = grow_size - 1;
|
|
|
|
let grow_blocks = (free_blocks + mask) & !mask;
|
2019-04-02 15:09:41 -04:00
|
|
|
Some(ResizeSize::blocks(grow_blocks))
|
|
|
|
} else {
|
|
|
|
None
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
const SUPERBLOCK_SIZE: usize = 1024;
|
|
|
|
pub struct Superblock([u8; SUPERBLOCK_SIZE]);
|
2019-04-03 16:05:09 -04:00
|
|
|
|
2019-04-02 15:09:41 -04:00
|
|
|
impl Superblock {
|
2019-04-03 16:05:09 -04:00
|
|
|
fn new() -> Self {
|
2019-04-02 15:09:41 -04:00
|
|
|
Superblock([0u8; SUPERBLOCK_SIZE])
|
|
|
|
}
|
|
|
|
|
2019-04-03 16:05:09 -04:00
|
|
|
pub fn load(path: impl AsRef<Path>, offset: u64) -> Result<Self> {
|
|
|
|
let mut sb = Self::new();
|
2019-04-02 15:09:41 -04:00
|
|
|
let mut file = File::open(path.as_ref())?;
|
|
|
|
file.seek(SeekFrom::Start(1024 + offset))?;
|
|
|
|
file.read_exact(&mut sb.0)?;
|
|
|
|
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 {
|
2019-04-03 16:05:09 -04:00
|
|
|
let lo = u64::from(self.u32(offset_lo));
|
|
|
|
let hi = u64::from(self.u32(offset_hi));
|
2019-04-02 15:09:41 -04:00
|
|
|
(hi << 32) | lo
|
|
|
|
}
|
|
|
|
|
|
|
|
fn at(&self, offset: usize) -> &[u8] {
|
|
|
|
&self.0[offset..]
|
|
|
|
}
|
|
|
|
}
|