使用std::fs::copy向SMB共享复制文件时遇Stale file handle问题
问题原因与解决方案
为什么std::fs::copy会失败
std::fs::copy的核心逻辑不仅是复制文件内容,还会自动同步源文件的元数据(包括权限、访问/修改时间戳等)。从你的strace输出可以明确看到:
- 程序成功创建目标文件(
openat返回文件描述符5) - 紧接着调用
statx尝试获取目标文件的元数据,却返回ESTALE(无效文件句柄) std::fs::copy将这个元数据获取错误视为致命错误,直接终止复制流程并抛出异常。
问题本质是:新版SMB协议挂载的CIFS共享,对已创建的文件通过文件描述符(AT_EMPTY_PATH方式)调用statx时存在兼容性问题,返回了ESTALE错误,而std::fs::copy依赖该操作完成元数据同步。
为什么std::io::copy能正常工作
std::io::copy的职责非常单一:仅在两个Read/Write对象之间复制字节流,不会主动处理元数据同步。虽然你的strace显示它也触发了statx调用(可能是Rust标准库内部的隐性检查逻辑),但这个错误并未被当成致命错误——只要文件的读写操作能正常执行,std::io::copy就会继续完成内容复制,忽略元数据相关的错误。
解决方案与替代方案
1. 直接使用std::io::copy(无需元数据同步)
如果你的场景不需要复制文件权限、时间戳等元数据,这是最简单的解决方案:
use std::fs::File; use std::io::copy; use std::path::Path; fn copy_file(source: &Path, target: &Path) -> std::io::Result<u64> { let mut source_file = File::open(source)?; let mut target_file = File::create(target)?; copy(&mut source_file, &mut target_file) }
2. 手动复制元数据(如需保留元数据)
如果需要同步元数据,可以手动获取源文件的元数据,再尝试设置到目标文件,同时捕获并忽略ESTALE错误:
use std::fs::{self, File}; use std::io::copy; use std::path::Path; fn copy_file_with_metadata(source: &Path, target: &Path) -> std::io::Result<u64> { // 复制文件内容 let mut source_file = File::open(source)?; let mut target_file = File::create(target)?; let copied_bytes = copy(&mut source_file, &mut target_file)?; // 尝试复制权限,忽略ESTALE错误 let source_meta = fs::metadata(source)?; if let Err(e) = fs::set_permissions(target, source_meta.permissions()) { if e.raw_os_error() != Some(116) { // 116对应ESTALE错误码 return Err(e); } } // 尝试同步访问/修改时间,同样忽略ESTALE错误 if let Err(e) = fs::set_times(target, source_meta.accessed()?, source_meta.modified()?) { if e.raw_os_error() != Some(116) { return Err(e); } } Ok(copied_bytes) }
3. 调整CIFS挂载参数
可以尝试修改挂载命令,添加参数规避statx的兼容性问题:
noserverino:禁用服务器inode编号,强制客户端生成inode,可能解决文件句柄相关错误nounix:禁用UNIX扩展,减少元数据交互的复杂度
示例挂载命令:
sudo mount -t cifs //remote /mountpoint -o nounix,noserverino
内容的提问来源于stack exchange,提问作者Aleksandar Dimitrov
相关产品推荐
相关产品推荐

