Skip to content

Commit 4c36afe

Browse files
authored
implement sync support (#701)
1 parent 24af748 commit 4c36afe

2 files changed

Lines changed: 91 additions & 6 deletions

File tree

TODO.md

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -54,10 +54,12 @@ reference workload that fails.
5454
Regression coverage includes a mountless two-handle test and a live,
5555
cross-process FUSE test in `tests/flock_test.rs` and `tests/live_mount.rs`.
5656

57-
- [ ] **3. `fsync`/`flush`/`fdatasync` are silent no-ops.**
58-
The `PathFilesystem` defaults return `Ok(())` and `EncFs` doesn't override
59-
them, so write→fsync→rename publishing isn't durable. Fix: `fsync`
60-
`File::sync_all`/`sync_data` on `handle.file`; `flush` likewise.
57+
- [x] **3. `fsync`/`flush`/`fdatasync` are silent no-ops.**
58+
Fixed: `EncFs::flush` calls `File::sync_all` on the open backing handle, and
59+
`EncFs::fsync` calls `File::sync_all` or `File::sync_data` according to the
60+
`datasync` flag. Backing I/O errors are returned to FUSE. Regression coverage
61+
in `src/fs.rs` uses a pipe that rejects synchronization, proving all three
62+
callbacks are no longer successful no-ops.
6163

6264
- [ ] **4. Directory rename under IV chaining is a non-atomic copy+delete that
6365
races readers/writers.**

src/fs.rs

Lines changed: 85 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1918,6 +1918,33 @@ impl PathFilesystem for EncFs {
19181918
Ok(self.write_impl(handle, offset, data)? as usize)
19191919
}
19201920

1921+
fn flush(
1922+
&self,
1923+
_path: Option<&Path>,
1924+
handle: &FileHandle,
1925+
_caller: &Request,
1926+
) -> Result<(), Errno> {
1927+
handle
1928+
.file
1929+
.sync_all()
1930+
.map_err(|e| e.raw_os_error().unwrap_or(libc::EIO).into())
1931+
}
1932+
1933+
fn fsync(
1934+
&self,
1935+
_path: Option<&Path>,
1936+
handle: &FileHandle,
1937+
datasync: bool,
1938+
_caller: &Request,
1939+
) -> Result<(), Errno> {
1940+
let sync_result = if datasync {
1941+
handle.file.sync_data()
1942+
} else {
1943+
handle.file.sync_all()
1944+
};
1945+
sync_result.map_err(|e| e.raw_os_error().unwrap_or(libc::EIO).into())
1946+
}
1947+
19211948
fn flock(
19221949
&self,
19231950
_path: Option<&Path>,
@@ -1979,9 +2006,16 @@ impl PathFilesystem for EncFs {
19792006
#[cfg(test)]
19802007
mod tests {
19812008
use super::{
1982-
FILE_STATE_SWEEP_FLOOR, FileStates, headerless_file_iv, is_apple_xattr,
1983-
lock_source_and_dest,
2009+
EncFs, FILE_STATE_SWEEP_FLOOR, FileHandle, FileState, FileStates, headerless_file_iv,
2010+
is_apple_xattr, lock_source_and_dest,
19842011
};
2012+
use crate::config::{EncfsConfig, Interface};
2013+
use crate::crypto::ssl::SslCipher;
2014+
use std::fs::File;
2015+
use std::os::fd::FromRawFd;
2016+
use std::path::PathBuf;
2017+
use std::sync::Arc;
2018+
use typed_fuse::{Caller, PathFilesystem};
19852019

19862020
fn table_len(states: &FileStates) -> usize {
19872021
states.table.lock().unwrap().entries.len()
@@ -2089,4 +2123,53 @@ mod tests {
20892123
assert!(!is_apple_xattr("user.encfs.attribute"));
20902124
assert!(!is_apple_xattr("com.example.attribute"));
20912125
}
2126+
2127+
fn test_fs() -> EncFs {
2128+
let interface = Interface {
2129+
name: "ssl/aes".to_string(),
2130+
major: 3,
2131+
minor: 0,
2132+
age: 0,
2133+
};
2134+
let mut cipher = SslCipher::new(&interface, 192).unwrap();
2135+
cipher.set_key(&[1u8; 24], &[2u8; 16]);
2136+
EncFs::new(
2137+
PathBuf::new(),
2138+
Box::new(cipher),
2139+
EncfsConfig::test_default(),
2140+
)
2141+
}
2142+
2143+
fn test_caller() -> Caller {
2144+
Caller {
2145+
pid: 1,
2146+
gid: 0,
2147+
uid: 0,
2148+
umask: 0,
2149+
}
2150+
}
2151+
2152+
#[test]
2153+
fn sync_callbacks_forward_backing_file_errors() {
2154+
let mut fds = [0; 2];
2155+
assert_eq!(unsafe { libc::pipe(fds.as_mut_ptr()) }, 0);
2156+
assert_eq!(unsafe { libc::close(fds[0]) }, 0);
2157+
2158+
// A pipe does not support synchronization. Using it as the backing
2159+
// file proves these callbacks issue real sync syscalls rather than
2160+
// inheriting PathFilesystem's successful no-op defaults. The errno
2161+
// differs across platforms, so the regression condition is simply that
2162+
// it propagates.
2163+
let handle = FileHandle {
2164+
file: unsafe { File::from_raw_fd(fds[1]) },
2165+
headerless_iv: 0,
2166+
state: Arc::new(FileState::new((0, 0))),
2167+
};
2168+
let fs = test_fs();
2169+
let caller = test_caller();
2170+
2171+
assert!(fs.flush(None, &handle, &caller).is_err());
2172+
assert!(fs.fsync(None, &handle, false, &caller).is_err());
2173+
assert!(fs.fsync(None, &handle, true, &caller).is_err());
2174+
}
20922175
}

0 commit comments

Comments
 (0)