1pub mod mount;
15pub mod xdr;
16pub mod rpc;
17pub mod client;
18#[cfg(feature = "krb5")]
19pub mod gss;
20#[cfg(feature = "tls")]
21pub(crate) mod tls_transport;
22#[cfg(feature = "rdma")]
23pub(crate) mod rdma_ffi;
24#[cfg(feature = "rdma")]
25pub(crate) mod rdma_msg;
26#[cfg(feature = "rdma")]
27pub(crate) mod rdma_transport;
28
29pub use mount::NfsBypassInfo;
30pub use mount::NfsSecurity;
31pub use mount::NfsTransportSecurity;
32pub use client::NfsCompoundClient;
33pub use client::NfsClientPool;
34
35pub const NFS_BYPASS_MAX_SIZE: u64 = 16 * 1024 * 1024;
37
38pub const NFS_BYPASS_WRITE_MAX: u64 = 1_048_576;
41
42#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
52pub enum NfsTransportType {
53 Tcp,
55 Tls,
57 Ktls,
59 Rdma,
61}
62
63impl NfsTransportType {
64 pub fn is_tls(&self) -> bool {
66 matches!(self, Self::Tls | Self::Ktls)
67 }
68
69 pub fn is_rdma(&self) -> bool {
71 matches!(self, Self::Rdma)
72 }
73
74 pub fn as_str(&self) -> &'static str {
76 match self {
77 Self::Tcp => "tcp",
78 Self::Tls => "tls",
79 Self::Ktls => "ktls",
80 Self::Rdma => "rdma",
81 }
82 }
83}
84
85pub mod idmap {
92 use std::sync::OnceLock;
93 use tracing::debug;
94
95 static IDMAP_DOMAIN: OnceLock<String> = OnceLock::new();
96
97 fn read_idmap_domain() -> String {
98 if let Ok(content) = std::fs::read_to_string("/etc/idmapd.conf") {
99 for line in content.lines() {
100 let trimmed = line.trim();
101 if let Some(rest) = trimmed.strip_prefix("Domain")
102 && let Some(val) = rest.trim().strip_prefix('=') {
103 let domain = val.trim().to_string();
104 if !domain.is_empty() {
105 debug!("idmap domain from /etc/idmapd.conf: {}", domain);
106 return domain;
107 }
108 }
109 }
110 }
111 if let Ok(hostname) = std::fs::read_to_string("/proc/sys/kernel/hostname")
112 && let Some(dot) = hostname.trim().find('.') {
113 let domain = hostname.trim()[dot + 1..].to_string();
114 debug!("idmap domain from hostname: {}", domain);
115 return domain;
116 }
117 debug!("idmap domain: fallback to localdomain");
118 "localdomain".to_string()
119 }
120
121 fn domain() -> &'static str {
122 IDMAP_DOMAIN.get_or_init(read_idmap_domain)
123 }
124
125 pub fn uid_to_owner(uid: u32) -> Option<String> {
126 let mut buf = [0u8; crate::constants::NFS_NSS_BUFFER_SIZE];
127 let mut pwd = std::mem::MaybeUninit::<libc::passwd>::uninit();
128 let mut result = std::ptr::null_mut::<libc::passwd>();
129 let ret = unsafe {
130 libc::getpwuid_r(
131 uid,
132 pwd.as_mut_ptr(),
133 buf.as_mut_ptr() as *mut libc::c_char,
134 buf.len(),
135 &mut result,
136 )
137 };
138 if ret == 0 && !result.is_null() {
139 let name = unsafe { std::ffi::CStr::from_ptr((*result).pw_name) };
140 if let Ok(name_str) = name.to_str() {
141 return Some(format!("{}@{}", name_str, domain()));
142 }
143 }
144 None
145 }
146
147 pub fn gid_to_group(gid: u32) -> Option<String> {
148 let mut buf = [0u8; crate::constants::NFS_NSS_BUFFER_SIZE];
149 let mut grp = std::mem::MaybeUninit::<libc::group>::uninit();
150 let mut result = std::ptr::null_mut::<libc::group>();
151 let ret = unsafe {
152 libc::getgrgid_r(
153 gid,
154 grp.as_mut_ptr(),
155 buf.as_mut_ptr() as *mut libc::c_char,
156 buf.len(),
157 &mut result,
158 )
159 };
160 if ret == 0 && !result.is_null() {
161 let name = unsafe { std::ffi::CStr::from_ptr((*result).gr_name) };
162 if let Ok(name_str) = name.to_str() {
163 return Some(format!("{}@{}", name_str, domain()));
164 }
165 }
166 None
167 }
168}
169
170#[derive(Debug, thiserror::Error)]
172pub enum NfsError {
173 #[error("TCP connection failed: {0}")]
174 ConnectionFailed(#[from] std::io::Error),
175
176 #[error("Session establishment failed: {0}")]
177 SessionFailed(String),
178
179 #[error("Stale file handle for {path}")]
180 StaleHandle { path: String },
181
182 #[error("NFS4 error {code}: {message}")]
183 Nfs4Error { code: u32, message: String },
184
185 #[error("XDR decode error: {0}")]
186 XdrDecode(String),
187
188 #[error("RPC error: {0}")]
189 RpcError(String),
190
191 #[error("Timeout after {0:?}")]
192 Timeout(std::time::Duration),
193
194 #[error("File too large for bypass: {size} > {max}")]
195 FileTooLarge { size: u64, max: u64 },
196
197 #[error("Kerberos auth required -- bypass unavailable")]
198 KerberosRequired,
199
200 #[error("RPC-over-TLS not supported by server (no STARTTLS response)")]
201 TlsNotSupported,
202
203 #[error("TLS handshake failed: {0}")]
204 TlsHandshakeFailed(String),
205
206 #[cfg(feature = "rdma")]
207 #[error("RDMA transport error: {0}")]
208 Rdma(String),
209}