Skip to content

Commit cdeeb83

Browse files
committed
implement write support in encfsr
1 parent a9d8127 commit cdeeb83

10 files changed

Lines changed: 1177 additions & 93 deletions

File tree

AGENTS.md

Lines changed: 32 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ This document provides comprehensive information for AI agents working in the En
88

99
- **Language**: Rust (Edition 2024)
1010
- **Primary Goal**: Read/write compatibility with legacy EncFS filesystems
11-
- **Status**: Beta (v2.0.0-beta.4) - functional for read/write but still maturing
11+
- **Status**: Beta (v2.0.0-beta.5) - functional for read/write but still maturing
1212

1313
### Key Characteristics
1414
- Encrypts individual files (not block devices)
@@ -93,6 +93,12 @@ task clippy
9393
./target/debug/encfsctl info /path/to/encrypted
9494
./target/debug/encfsctl decode /path/to/encrypted encrypted_filename
9595
./target/debug/encfsctl cat /path/to/encrypted encrypted_filename
96+
97+
# Reverse encryption: plaintext source -> encrypted virtual view
98+
./target/debug/encfsr /path/to/source/.encfs7 /path/to/source /path/to/mountpoint
99+
100+
# Opt in to writes through the encrypted reverse view
101+
./target/debug/encfsr --write /path/to/source/.encfs7 /path/to/source /path/to/mountpoint
96102
```
97103

98104
### Installation
@@ -108,11 +114,13 @@ encfs/
108114
├── src/ # Rust source code
109115
│ ├── main.rs # Main encfs binary (FUSE mount)
110116
│ ├── encfsctl.rs # Control utility binary
117+
│ ├── encfsr.rs # Reverse-mode FUSE binary
111118
│ ├── lib.rs # Library entry point
112119
│ ├── config.rs # Config file parsing (V4/V5/V6)
113120
│ ├── config_binary.rs # Binary config format parser
114121
│ ├── constants.rs # Global constants
115122
│ ├── fs.rs # FUSE filesystem implementation
123+
│ ├── reverse_fs.rs # Reverse-mode FUSE implementation
116124
│ └── crypto/ # Cryptographic operations
117125
│ ├── mod.rs # Crypto module exports
118126
│ ├── ssl.rs # Legacy cipher wrapper (RustCrypto)
@@ -141,7 +149,7 @@ encfs/
141149

142150
2. **`config.rs`**: Configuration file handling
143151
- `EncfsConfig`: Main config struct
144-
- `ConfigType`: Enum for V3/V4/V5/V6 formats
152+
- `ConfigType`: Enum for V3/V4/V5/V6/V7 formats
145153
- `Interface`: Cipher/naming algorithm interface
146154
- Supports XML (V6) and binary (V4/V5) formats
147155
- XML uses Boost Serialization format for compatibility
@@ -172,6 +180,14 @@ encfs/
172180
- Subcommands: info, passwd, decode, encode, cat, ls, showkey, export
173181
- Standalone utility for inspecting/manipulating encrypted filesystems
174182

183+
8. **`reverse_fs.rs` / `encfsr.rs`**: Reverse encryption
184+
- Presents an encrypted V7 view of a plaintext source directory
185+
- Read-only by default; `encfsr --write` enables transactional writes
186+
- Requires `unique_iv = false`; create a compatible V7 config with
187+
`encfsctl new --no-unique-iv <source-dir>`
188+
- Stages ciphertext writes and validates authenticated blocks before applying
189+
changes to the plaintext source
190+
175191
## Naming Conventions
176192

177193
### Rust Standard Conventions
@@ -304,7 +320,16 @@ Three methods (in order of precedence):
304320
2. `--stdinpass`: Read from stdin
305321
3. Default: Interactive prompt via `rpassword`
306322

307-
### 9. Validation Requirements
323+
### 9. Reverse Mode Writes
324+
- `encfsr` only accepts V7 configs and rejects `unique_iv = true`.
325+
- Use `--write` explicitly; regular reverse mounts remain read-only.
326+
- `encfsctl new --no-unique-iv` remains compatible with V7 AES-GCM-SIV tags,
327+
filename IV chaining, and external IV chaining.
328+
- Incomplete or invalid ciphertext is rejected during flush/close without
329+
applying it to the plaintext source. Direct source changes while a write is
330+
staged cause the commit to fail with a conflict.
331+
332+
### 10. Validation Requirements
308333
The `EncfsConfig::validate()` method enforces:
309334
- `plain_data` must be false (not supported)
310335
- `unique_iv` may be true or false (filesystem supports both)
@@ -314,13 +339,13 @@ The `EncfsConfig::validate()` method enforces:
314339
- `block_mac_bytes` must be 0-8
315340
- Block size must be larger than MAC overhead
316341

317-
### 10. Logging
342+
### 11. Logging
318343
- Uses `env_logger` crate
319344
- Controlled by `RUST_LOG` environment variable
320345
- `-v` flag sets debug level
321346
- `-d` flag sets debug + foreground mode
322347

323-
### 11. Daemonization
348+
### 12. Daemonization
324349
- Uses `daemonize` crate
325350
- Automatic unless `-f` (foreground) or `-d` (debug) flag
326351
- Happens after password validation, before FUSE mount
@@ -530,6 +555,6 @@ task test-live # Live mount tests
530555

531556
---
532557

533-
**Last Updated**: July 22, 2026
534-
**EncFS Version**: 2.0.0-beta.4
558+
**Last Updated**: July 28, 2026
559+
**EncFS Version**: 2.0.0-beta.5
535560
**Rust Edition**: 2024

Cargo.lock

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ exclude = ["fuzz"]
44

55
[package]
66
name = "encfs"
7-
version = "2.0.0-beta.4"
7+
version = "2.0.0-beta.5"
88
edition = "2024"
99

1010
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
@@ -42,6 +42,7 @@ sha1 = "0.11"
4242
sha2 = "0.11"
4343
zeroize = { version = "1", features = ["derive"] }
4444
sysinfo = { version = "0.39.6", default-features = false, features = ["system"] }
45+
tempfile = "3"
4546

4647
[[bin]]
4748
name = "encfsctl"
@@ -54,7 +55,6 @@ path = "src/encfsr.rs"
5455
[dev-dependencies]
5556
serde_json = "1"
5657
tar = "0.4"
57-
tempfile = "3"
5858

5959
[[bench]]
6060
name = "filesystem"

ChangeLog

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,9 @@
1+
v2.0.0-beta.5 / 2026-07-28
2+
==================
3+
* Add opt-in writable reverse mounts with `encfsr --write`
4+
* Stage and authenticate reverse writes before applying them to the plaintext source
5+
* Support V7 reverse-write configs created with `encfsctl new --no-unique-iv`
6+
17
v2.0.0-beta.4 / 2026-07-22
28
==================
39
* Replace async-fuse with libfuse-sys for improved Linux and macOS performance

README.md

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -137,7 +137,7 @@ fusermount -u ~/mount # Linux
137137
## Reverse encryption mode (encfsr)
138138

139139
The **encfsr** binary provides *reverse* encryption: your **plaintext** files
140-
live on disk in a source directory, and encfsr mounts a **read-only** virtual
140+
live on disk in a source directory, and encfsr mounts a **read-only by default** virtual
141141
filesystem that exposes the **encrypted** view of that directory. Use this when
142142
you want to back up or sync an encrypted representation of local data (e.g. to
143143
an untrusted or cloud storage) without storing plaintext there.
@@ -149,8 +149,9 @@ an untrusted or cloud storage) without storing plaintext there.
149149

150150
- A **V7** EncFS config (e.g. `.encfs7`). Older configs are not supported.
151151
- Config should be created **without** per-file IV headers: use
152-
`encfsctl new --no-unique-iv ...` so the content is deterministic and suitable
153-
for reverse mode.
152+
`encfsctl new --no-unique-iv ...`. This is required by encfsr, including
153+
writable reverse mounts; authenticated V7 block encryption and IV chaining
154+
remain supported.
154155

155156
### Usage
156157

@@ -164,6 +165,15 @@ Example: plaintext in `~/Documents`, encrypted view at `/mnt/enc`:
164165
encfsr ~/Documents/.encfs7 ~/Documents /mnt/enc
165166
```
166167

168+
To restore through the encrypted view, opt in explicitly to writable reverse
169+
mode. Writes are staged and authenticated before they are applied to the
170+
plaintext source, so a malformed or incomplete encrypted write fails at flush
171+
or close rather than corrupting the source:
172+
173+
```bash
174+
encfsr --write ~/Documents/.encfs7 ~/Documents /mnt/enc
175+
```
176+
167177
Then copy or sync from `/mnt/enc` to your backup/cloud target; the content and filenames there are encrypted.
168178

169179
Options (same as encfs where applicable):

locales/help.yml

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -218,9 +218,9 @@ help.encfsctl.speed:
218218
de: "Versionsinformationen anzeigen und unterstützte Verschlüsselungsalgorithmen benchmarken"
219219

220220
help.encfsr.about:
221-
en: "EncFS Reverse - Virtual Encrypted Read-Only Filesystem"
222-
fr: "EncFS Reverse - Système de fichiers chiffré virtuel en lecture seule"
223-
de: "EncFS Reverse - Virtuelles verschlüsseltes schreibgeschütztes Dateisystem"
221+
en: "EncFS Reverse - Virtual Encrypted Filesystem"
222+
fr: "EncFS Reverse - Système de fichiers chiffré virtuel"
223+
de: "EncFS Reverse - Virtuelles verschlüsseltes Dateisystem"
224224
help.encfsr.config:
225225
en: "EncFS config file (e.g. .encfs6.xml or .encfs7)"
226226
fr: "Fichier de configuration EncFS (par ex. .encfs6.xml ou .encfs7)"
@@ -229,6 +229,10 @@ help.encfsr.source:
229229
en: "Source directory (plaintext)"
230230
fr: "Répertoire source (texte en clair)"
231231
de: "Quellverzeichnis (Klartext)"
232+
help.encfsr.write:
233+
en: "Allow transactional writes through the encrypted reverse view"
234+
fr: "Autoriser les écritures transactionnelles via la vue chiffrée inversée"
235+
de: "Transaktionale Schreibvorgänge über die verschlüsselte Reverse-Ansicht erlauben"
232236
help.encfsr.fuse_opts:
233237
en: "FUSE options (e.g. -o allow_other)"
234238
fr: "Options FUSE (ex : -o allow_other)"

src/encfsr.rs

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,10 @@ fn help_encfsr_foreground() -> String {
3737
t!("help.encfs.foreground").to_string()
3838
}
3939

40+
fn help_encfsr_write() -> String {
41+
t!("help.encfsr.write").to_string()
42+
}
43+
4044
fn help_encfsr_fuse_opts() -> String {
4145
t!("help.encfsr.fuse_opts").to_string()
4246
}
@@ -68,6 +72,10 @@ struct Args {
6872
#[arg(short = 'f', long, help = help_encfsr_foreground())]
6973
foreground: bool,
7074

75+
/// Allow writes to the plaintext source through the encrypted view.
76+
#[arg(short = 'w', long, help = help_encfsr_write())]
77+
write: bool,
78+
7179
/// FUSE options passed directly to the FUSE layer (e.g. -o allow_other).
7280
/// Place these after -- or use trailing arguments directly.
7381
#[arg(trailing_var_arg = true, allow_hyphen_values = true, help = help_encfsr_fuse_opts())]
@@ -224,12 +232,15 @@ fn main() -> Result<()> {
224232
config,
225233
config_bytes,
226234
config_metadata,
235+
encfs::reverse_fs::ReverseFsOptions {
236+
writable: args.write,
237+
},
227238
);
228239

229-
// Build FUSE options: always mount read-only at kernel level (FUSE-01)
230-
// plus default_permissions, then pass through any user-provided fuse_opts.
240+
// The encrypted view is read-only unless the user explicitly opted into
241+
// reverse writes. A supplied `-o ro` may still tighten a writable mount.
231242
let mut mount_config = typed_fuse::mount::MountConfig {
232-
read_only: true,
243+
read_only: !args.write,
233244
default_permissions: true,
234245
..typed_fuse::mount::MountConfig::new("encfsr")
235246
};
@@ -255,6 +266,10 @@ fn main() -> Result<()> {
255266
};
256267
mount_config.parse_option_words(words);
257268
}
269+
if !args.write {
270+
// Do not let `-o rw` turn the safe default into a writable mount.
271+
mount_config.read_only = true;
272+
}
258273

259274
// Pre-check the mount point so a bad one is reported in the user's
260275
// locale; `Session::mount` repeats the check (and works around the macOS

0 commit comments

Comments
 (0)