redoxfs 0.9.0

The Redox Filesystem
Documentation
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)
    );
}