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

@@ -2,7 +2,7 @@ use super::config::apply::try_apply_lock;
use super::*;
use crate::msd::{
DiskModeRequest, DownloadProgress, DriveFile, DriveInfo, DriveInitRequest,
DiskModeRequest, DownloadProgress, DriveFile, DriveFileAccess, DriveInfo, DriveInitRequest,
ImageDownloadRequest, ImageInfo, ImageManager, ImageMountRequest, MsdErrorCode, MsdState,
MsdStateResponse, VentoyDrive, MIN_DRIVE_SIZE_MB,
};
@@ -420,16 +420,26 @@ pub async fn msd_drive_info(State(state): State<Arc<AppState>>) -> Result<Json<D
let drive_path = config.msd.drive_path();
let drive = VentoyDrive::new(drive_path);
if !drive.exists() {
// 404: drive image file does not exist at all — truly not initialized
return Err(MsdErrorCode::MsdDriveNotInitialized.into());
let msd_guard = state.msd.read().await;
let connected = match msd_guard.as_ref() {
Some(controller) => controller.is_drive_connected().await,
None => false,
};
// Never parse the filesystem while the USB host owns it. Metadata is safe
// to read and still lets the UI show the backing image capacity.
let info = if connected {
drive.raw_info(DriveFileAccess::BlockedWhileConnected)
} else {
drive.info().await
}
.map_err(|error| operation_failed("read virtual drive info", error))?;
if let Some(controller) = msd_guard.as_ref() {
controller.set_drive_info(Some(info.clone())).await;
}
drive
.info()
.await
.map(Json)
.map_err(|error| operation_failed("read virtual drive info", error))
Ok(Json(info))
}
/// Initialize Ventoy drive
@@ -439,7 +449,6 @@ pub async fn msd_drive_init(
payload: std::result::Result<Json<DriveInitRequest>, JsonRejection>,
) -> Result<Json<DriveInfo>> {
let req = parse_msd_json(payload)?;
assert_drive_not_connected(&state).await?;
let config = state.config.get();
let msd_dir = config.msd.msd_dir_path();
@@ -449,6 +458,16 @@ pub async fn msd_drive_init(
})?;
validate_drive_init_size(req.size_mb, disk_space.available)?;
// Mount/unmount handlers also take this outer write lock. Holding it
// across image creation prevents a mount from racing the destructive
// reinitialization after the connected-state check.
let msd_guard = state.msd.write().await;
if let Some(controller) = msd_guard.as_ref() {
if controller.is_drive_connected().await {
return Err(MsdErrorCode::MsdDriveConnected.into());
}
}
let drive_path = config.msd.drive_path();
let drive = VentoyDrive::new(drive_path);
@@ -456,6 +475,9 @@ pub async fn msd_drive_init(
.init(req.size_mb)
.await
.map_err(|error| operation_failed("initialize virtual drive", error))?;
if let Some(controller) = msd_guard.as_ref() {
controller.set_drive_info(Some(info.clone())).await;
}
Ok(Json(info))
}
@@ -471,14 +493,15 @@ pub async fn msd_drive_delete(State(state): State<Arc<AppState>>) -> Result<Json
return Err(MsdErrorCode::MsdDriveConnected.into());
}
}
drop(msd_guard);
// Delete the drive file
let drive_path = config.msd.drive_path();
if drive_path.exists() {
std::fs::remove_file(&drive_path)
.map_err(|error| classify_storage_error("delete virtual drive", error))?;
}
if let Some(controller) = msd_guard.as_ref() {
controller.set_drive_info(None).await;
}
Ok(Json(LoginResponse {
success: true,