fix(msd): 区分虚拟磁盘挂载能力与文件访问状态

使用原始文件元数据挂载虚拟磁盘,允许主机使用网页文件管理不支持的文件系统。连接期间只读取元数据;初始化和删除持有外层写锁,并同步控制器缓存。

API 的 used/free 改为可空值,新增 file_access 表示 available、unsupported、blocked_while_connected 或 unknown,需要前端配套处理。补充状态序列化、元数据校验和文件系统兼容测试。
This commit is contained in:
mofeng-git
2026-09-05 14:10:53 +08:00
parent d67a3ab7f8
commit b573f478fc
6 changed files with 300 additions and 90 deletions

View File

@@ -8,9 +8,10 @@ use tracing::{debug, info, warn};
use super::image::ImageManager;
use super::monitor::MsdHealthMonitor;
use super::types::{
DiskMode, DownloadProgress, DownloadStatus, DriveInfo, ImageInfo, MountedMedia,
MountedMediaKind, MsdState,
DiskMode, DownloadProgress, DownloadStatus, DriveFileAccess, DriveInfo, ImageInfo,
MountedMedia, MountedMediaKind, MsdState,
};
use super::ventoy_drive::VentoyDrive;
use crate::error::{AppError, MsdErrorCode, Result};
use crate::otg::{MsdFunction, MsdLunConfig, OtgService};
@@ -83,14 +84,9 @@ impl MsdController {
state.available = true;
if self.drive_path.exists() {
if let Ok(metadata) = std::fs::metadata(&self.drive_path) {
let drive_info = DriveInfo {
size: metadata.len(),
used: 0,
free: metadata.len(),
initialized: true,
path: self.drive_path.clone(),
};
if let Ok(drive_info) =
VentoyDrive::new(self.drive_path.clone()).raw_info(DriveFileAccess::Unknown)
{
state.drive_info = Some(drive_info.clone());
debug!(
"Found existing virtual drive: {}",
@@ -199,28 +195,6 @@ impl MsdController {
self.assert_available(&state).await?;
if !self.drive_path.exists() {
self.monitor
.report_error("Virtual drive not initialized", "drive_not_found")
.await;
return Err(MsdErrorCode::MsdDriveNotInitialized.into());
}
let drive_info = state.drive_info.clone().or_else(|| {
std::fs::metadata(&self.drive_path)
.ok()
.map(|metadata| DriveInfo {
size: metadata.len(),
used: 0,
free: metadata.len(),
initialized: true,
path: self.drive_path.clone(),
})
});
if state.drive_info.is_none() {
state.drive_info = drive_info.clone();
}
if state
.mounted_media
.iter()
@@ -229,8 +203,22 @@ impl MsdController {
return Err(MsdErrorCode::MsdMediaAlreadyMounted.into());
}
let drive_info =
drive_info.ok_or_else(|| AppError::from(MsdErrorCode::MsdDriveNotInitialized))?;
let drive_info = match self.drive_mount_info() {
Ok(info) => info,
Err(error) => {
if matches!(
&error,
AppError::Msd(msd) if msd.code() == MsdErrorCode::MsdDriveNotInitialized
) {
self.monitor
.report_error("Virtual drive not initialized", "drive_not_found")
.await;
}
return Err(error);
}
};
state.drive_info = Some(drive_info.clone());
let lun = Self::lowest_free_lun(&state)
.ok_or_else(|| AppError::from(MsdErrorCode::MsdMediaSlotsFull))?;
@@ -240,6 +228,8 @@ impl MsdController {
return Err(e);
}
state.mounted_media.push(media);
state.drive_info =
Some(drive_info.with_file_access(DriveFileAccess::BlockedWhileConnected));
info!(
"Mounted virtual drive on LUN {}: {}",
@@ -254,6 +244,15 @@ impl MsdController {
Ok(())
}
fn drive_mount_info(&self) -> Result<DriveInfo> {
VentoyDrive::new(self.drive_path.clone()).raw_info(DriveFileAccess::Unknown)
}
pub async fn set_drive_info(&self, drive_info: Option<DriveInfo>) {
self.state.write().await.drive_info = drive_info;
self.mark_device_info_dirty().await;
}
async fn assert_available(&self, state: &MsdState) -> Result<()> {
if !state.available {
self.monitor
@@ -293,6 +292,16 @@ impl MsdController {
}
fn reset_mounts_for_mode(state: &mut MsdState, disk_mode: DiskMode) {
if state
.mounted_media
.iter()
.any(|media| media.kind == MountedMediaKind::Drive)
{
state.drive_info = state
.drive_info
.take()
.map(|info| info.with_file_access(DriveFileAccess::Unknown));
}
state.disk_mode = disk_mode;
state.mounted_media.clear();
}
@@ -397,6 +406,12 @@ impl MsdController {
self.disconnect_lun(media.lun).await?;
state.mounted_media.remove(index);
if media.kind == MountedMediaKind::Drive {
state.drive_info = state
.drive_info
.take()
.map(|info| info.with_file_access(DriveFileAccess::Unknown));
}
info!("Unmounted media");
drop(state);
@@ -490,6 +505,16 @@ impl MsdController {
disconnected.push(media.clone());
}
if state
.mounted_media
.iter()
.any(|media| media.kind == MountedMediaKind::Drive)
{
state.drive_info = state
.drive_info
.take()
.map(|info| info.with_file_access(DriveFileAccess::Unknown));
}
state.mounted_media.clear();
info!("Disconnected all mounted media");
@@ -744,6 +769,29 @@ mod tests {
assert!(state.mounted_media.is_empty());
}
#[tokio::test]
async fn drive_mount_metadata_ignores_cached_drive_info() {
let temp_dir = TempDir::new().unwrap();
let controller = MsdController::new(Arc::new(OtgService::new()), temp_dir.path());
std::fs::create_dir_all(&controller.ventoy_dir).unwrap();
std::fs::write(&controller.drive_path, vec![0u8; 128]).unwrap();
controller.state.write().await.drive_info = Some(DriveInfo::from_raw(
controller.drive_path.clone(),
64,
DriveFileAccess::Available,
));
std::fs::write(&controller.drive_path, vec![0u8; 256]).unwrap();
let info = controller.drive_mount_info().unwrap();
assert_eq!(info.size, 256);
assert_eq!(info.used, None);
assert_eq!(info.file_access, DriveFileAccess::Unknown);
let media = MountedMedia::drive(0, &info);
let config = MsdController::media_config(&media);
assert_eq!(config.file, controller.drive_path);
}
#[test]
fn single_disk_mode_only_exposes_lun_zero() {
let mut state = MsdState::default();
@@ -842,13 +890,7 @@ mod tests {
let temp_dir = TempDir::new().unwrap();
let drive_path = temp_dir.path().join("ventoy.img");
std::fs::write(&drive_path, b"drive").unwrap();
let drive = DriveInfo {
size: 5,
used: 0,
free: 5,
initialized: true,
path: drive_path,
};
let drive = DriveInfo::from_raw(drive_path, 5, DriveFileAccess::Unknown);
let mut state = MsdState::default();
MsdController::reset_mounts_for_mode(&mut state, DiskMode::Multi);
state.mounted_media.push(MountedMedia::drive(0, &drive));
@@ -900,13 +942,11 @@ mod tests {
let image_path = temp_dir.path().join("test.img");
std::fs::write(&image_path, b"img").unwrap();
let image = ImageInfo::new("test".into(), "test.img".into(), image_path, 3);
let drive = DriveInfo {
size: 5,
used: 0,
free: 5,
initialized: true,
path: temp_dir.path().join("ventoy.img"),
};
let drive = DriveInfo::from_raw(
temp_dir.path().join("ventoy.img"),
5,
DriveFileAccess::Unknown,
);
let mut state = MsdState::default();
state
.mounted_media