3 releases

0.1.2 Jan 2, 2020
0.1.1 Jan 2, 2020
0.1.0 Jan 2, 2020

#1223 in Filesystem

MIT license

17KB
225 lines

vblk

Documentation Crates.io

This crate provides an interface to create virtual block devices which call into your Rust code for read and write operations. This is similar to FUSE except that instead of exposing an entire filesystem, you only expose a single fixed-size file as a block device. This can be handy if you are interfacing with a remote block device but don't have any convenient means of mounting it for whatever reason, or for prototyping block device drivers.

This is achieved by (ab)using the Linux kernel's NBD driver rather than implementing a new generic block device driver; a basic NBD client is started in the Rust application serving NBD requests which are shuttled to and from the kernel driver through a Unix domain socket. Most of the time this is done through forking; in this crate a separate thread is started instead for hosting the kernel-side NBD server.

This crate only works on Linux 2.6+ and requires that the NBD kernel module be installed and loaded. It also requires root by default. Newer NBD features such as the FLUSH and TRIM commands may only be available in later Linux kernel versions; for older versions the Rust handlers for these commands will simply never be called.

Usage

Assuming you have the proper permissions and the NBD module is loaded, this example will mount a virtual block device on /dev/nbd0 which will read 0xDEADBEEF across the entire block device. Since no write handler is specified, write requests to the block device will be denied. You can find and run this example in examples/deadbeef.rs, and there is a ramdisk example showing how writes work in examples/ramdisk.rs.

use std::io::Error;
use vblk::{mount, BlockDevice};

struct DeadbeefDevice;

impl BlockDevice for DeadbeefDevice {
    fn read(&mut self, offset: u64, bytes: &mut [u8]) -> Result<(), Error> {
        for (index, byte) in bytes.iter_mut().enumerate() {
            *byte = match (index as u64 + offset) % 4 {
                0 => 0xDE,
                1 => 0xAD,
                2 => 0xBE,
                _ => 0xEF,
            };
        }

        Ok(())
    }

    fn block_size(&self) -> u32 {
        1024
    }

    fn blocks(&self) -> u64 {
        4096 // 4MB device
    }
}

fn main() {
    unsafe { mount(&mut DeadbeefDevice, "/dev/nbd0", |_device| Ok(())).unwrap() };
}

Once it's running, you should be able to access and read the block device:

# xxd /dev/nbd0 | head -n 2
00000000: dead beef dead beef dead beef dead beef  ................
00000010: dead beef dead beef dead beef dead beef  ................
# blockdev --getsize64 /dev/nbd0
4194304

But attempting to write to the block device will fail as we haven't provided a write handler, as observed below. Note that the oflag=dsync parameter is important here, otherwise the writes will be cached and asynchronously written back by the kernel and you will find the write errors in dmesg instead.

# dd oflag=dsync if=/dev/zero of=/dev/nbd0 bs=1024 count=4096
dd: error writing '/dev/nbd0': Input/output error
1+0 records in
0+0 records out
0 bytes copied, 0.000263995 s, 0.0 kB/s

Unmounting

The virtual block device will only get its unmount handler called if the NBD connection is broken gracefully, so just sending Ctrl+C to your application will by default not do that. The callback you pass to the mount function yields an owned Device struct, which among other things has an unmount method on it; the struct is intended to be stored somewhere and used whenever your application needs to shut down. You must not block in the callback; the block device will not be mounted until it returns.

The examples show how to use this with the ctrlc crate, by intercepting Ctrl+C and unmounting the block device, which will cause the mount method to return gracefully.

Permissions

By default on most distributions only root (or users in the disk group) can interface with the NBD kernel module and NBD block devices. I haven't tried this yet but it should be possible to configure the NBD device driver through e.g. udev rules depending on your environment.

License

This software is provided under the MIT license.

Dependencies

~4.5MB
~96K SLoC