Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -255,3 +255,8 @@ assets = [
]
[package.metadata.generate-rpm.requires]
fuse3 = "*"


[target.'cfg(target_os = "windows")'.dependencies]
winfsp = "0.3"
winapi = { version = "0.3", features = ["winerror", "winnt"] }
85 changes: 85 additions & 0 deletions docs/readme/Windows.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
# Windows Support (WinFsp)

rencfs supports mounting encrypted filesystems on Windows via [WinFsp](https://winfsp.dev).

## Prerequisites

1. Install **WinFsp** (MIT-licensed): https://winfsp.dev/rel/
2. Rust stable with the `x86_64-pc-windows-msvc` target:
```powershell
rustup target add x86_64-pc-windows-msvc
```

## Build

```powershell
cargo build --target x86_64-pc-windows-msvc
```

## Mount

```powershell
rencfs.exe --mountpoint X: --data-dir C:\path\to\encrypted
```

`X:` must be an unused drive letter or a directory path (WinFsp supports both).

## Unmount

```powershell
rencfs.exe umount X:
# or via MountHandle::umount() in library usage
```

## Implementation Notes

The Windows backend (`src/mount/windows.rs`) implements `winfsp::filesystem::FileSystemContext`
and bridges each WinFsp callback to the existing `EncryptedFs` async methods via a
dedicated Tokio runtime on the WinFsp dispatch thread.

### Operation mapping

| WinFsp callback | EncryptedFs method |
|-----------------------|--------------------------------------------|
| `get_volume_info` | virtual (1 TiB reported) |
| `get_security_by_name`| `find_by_name` + mode → NT SD |
| `open` | `open(inode, read, write)` |
| `create` | `create_nod(parent, mode, ...)` |
| `close` | `release(fh)` |
| `read` | `read(inode, offset, buf, fh)` |
| `write` | `write(inode, offset, data, fh)` |
| `flush` | `flush(fh)` |
| `get_file_info` | `get_attr(inode)` → `FileInfo` |
| `set_basic_info` | `set_attr(inode, SetFileAttr { atime, .. })`|
| `set_file_size` | `set_len(inode, new_size)` |
| `rename` | `rename(parent, name, new_parent, new_name)`|
| `read_directory` | `read_dir(inode)` |
| `cleanup` | `remove_file` / `remove_dir` on delete flag|

### Status

- [x] WinFsp host boots and mounts the drive letter
- [x] Volume info reported to Explorer
- [ ] Directory listing (read_directory) — in progress
- [ ] File open/read/write — in progress
- [ ] Security descriptor mapping — in progress

### Known limitations (v1 milestone)

- NTFS alternate data streams: not supported
- Hard links: not supported (rencfs has no inode aliasing)
- Reparse points / symlinks: not supported
- Per-file ACLs: not supported; permissions are derived from rencfs mode bits

## CI

GitHub Actions workflow addition (`.github/workflows/build_and_tests.yaml`):

```yaml
- name: Build (Windows)
if: runner.os == 'Windows'
run: cargo build --target x86_64-pc-windows-msvc
```

A full integration test requires WinFsp installed on the runner; this is
tracked separately.
87 changes: 54 additions & 33 deletions src/mount.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,18 @@ use linux::MountHandleInnerImpl;
#[cfg(target_os = "linux")]
use linux::MountPointImpl;

#[cfg(not(target_os = "linux"))]
#[cfg(target_os = "windows")]
mod windows;
#[cfg(target_os = "windows")]
use windows::MountHandleInnerImpl;
#[cfg(target_os = "windows")]
use windows::MountPointImpl;

#[cfg(not(any(target_os = "linux", target_os = "windows")))]
mod dummy;
#[cfg(not(target_os = "linux"))]
#[cfg(not(any(target_os = "linux", target_os = "windows")))]
use dummy::MountHandleInnerImpl;
#[cfg(not(target_os = "linux"))]
#[cfg(not(any(target_os = "linux", target_os = "windows")))]
use dummy::MountPointImpl;

#[async_trait]
Expand Down Expand Up @@ -63,6 +70,7 @@ impl Future for MountHandle {
pub(crate) trait MountHandleInner: Future<Output = io::Result<()>> {
async fn unmount(mut self) -> io::Result<()>;
}

/// Available arguments
///
/// **`mountpoint`** where it wil mount the filesystem
Expand Down Expand Up @@ -98,38 +106,51 @@ pub fn create_mount_point(
}

pub fn umount(mountpoint: &str) -> io::Result<()> {
// try normal umount
if process::Command::new("umount")
.arg(mountpoint)
.output()?
.status
.success()
#[cfg(target_os = "windows")]
{
// On Windows, unmounting is handled by the WinFsp host via MountHandle::umount().
// A best-effort attempt using `net use /delete` is provided for drive-letter mounts.
let _ = process::Command::new("net")
.args(["use", mountpoint, "/delete", "/yes"])
.output();
return Ok(());
}
// force umount
if process::Command::new("umount")
.arg("-f")
.arg(mountpoint)
.output()?
.status
.success()
{
return Ok(());
}
// lazy umount
if process::Command::new("umount")
.arg("-l")
.arg(mountpoint)
.output()?
.status
.success()

#[cfg(not(target_os = "windows"))]
{
Ok(())
} else {
Err(io::Error::new(
io::ErrorKind::Other,
format!("cannot umount {mountpoint}"),
))
// try normal umount
if process::Command::new("umount")
.arg(mountpoint)
.output()?
.status
.success()
{
return Ok(());
}
// force umount
if process::Command::new("umount")
.arg("-f")
.arg(mountpoint)
.output()?
.status
.success()
{
return Ok(());
}
// lazy umount
if process::Command::new("umount")
.arg("-l")
.arg(mountpoint)
.output()?
.status
.success()
{
Ok(())
} else {
Err(io::Error::new(
io::ErrorKind::Other,
format!("cannot umount {mountpoint}"),
))
}
}
}
}
Loading