← All projects

winext

A WinFsp filesystem driver that mounts an ext4 image as a drive letter on Windows. Around 250 lines so far - it lists directories and reads metadata, and it crashes on more than that.

WIP Rust WinFsp ext4 Windows

The problem

I wanted to open ext4 images on Windows, which has no native ext4 support. Linux has ext4-lwext4, a clean userspace ext4 implementation. This sits between them: WinFsp on one side, an ext4 filesystem on the other, translating between the two sets of concepts. Anything that speaks normal Windows file APIs gets ext4 for free.

Why these choices

The mount path is six lines

Most of the complexity of this project is hidden inside the two calls that matter. What I wrote by hand is the FileSystemContext impl underneath.

rustmain.rs
let mut host = WinExtFs::new(context, crc32(CRC32_INIT, &data) as u32);
host.host.mount("Z:").unwrap();

host.host.start().unwrap();

loop {}

The serial number is a CRC of the image, so remounting the same image gives Windows a consistent identity. The final loop {} is not a bug - it keeps the process alive because the host has no other reason to hold the thread.

Windows wants a FILETIME

ext4 hands back Unix timestamps, Windows wants 100-nanosecond intervals since 1601. That offset is the number in the middle, and getting it wrong produces timestamps that land in the wrong century with no error at all.

rustsystem.rs
let mut volume_params = VolumeParams::new();
volume_params.filesystem_name("ext4");
volume_params.volume_creation_time(windows_filetime);
volume_params.volume_serial_number(serial_number);

Directory reads are a marker walk

Windows pages through directories using a marker: "give me what comes after this name." The trap is that you iterate the whole directory on every call and skip until you hit the marker. Get that wrong and Explorer shows a directory with everything but the first entry.

rustsystem.rs
let mut marker_passed = marker.is_none();
let marker_name = match marker.inner_as_cstr() {
    Some(m) => Some(m.to_string_lossy()),
    None => None,
};

for dir in directories {
    if !marker_passed {
        if marker_name == Some(name) { marker_passed = true; }
        continue;
    }

    let mut dirinfo: DirInfo<255> = DirInfo::new();
    if dirinfo.set_name(name.clone()).is_err() { continue; }

    dirinfo.append_to_buffer(buffer, &mut bytes_transferred);
}

Current limitations