mirror of
https://github.com/mofeng-git/One-KVM.git
synced 2026-07-29 09:41:45 +08:00
fix: 完善虚拟U盘创建错误提示 #275
This commit is contained in:
@@ -11,8 +11,6 @@ clap = { version = "4", features = ["derive"] }
|
|||||||
|
|
||||||
# Error handling
|
# Error handling
|
||||||
thiserror = "2"
|
thiserror = "2"
|
||||||
|
|
||||||
[dev-dependencies]
|
|
||||||
tempfile = "3"
|
tempfile = "3"
|
||||||
|
|
||||||
[profile.release]
|
[profile.release]
|
||||||
|
|||||||
@@ -28,8 +28,15 @@ impl VentoyImage {
|
|||||||
path.display()
|
path.display()
|
||||||
);
|
);
|
||||||
|
|
||||||
// Create sparse file
|
// Build beside the destination so a failed create cannot leave a partial image.
|
||||||
let mut file = File::create(path)?;
|
let parent = path
|
||||||
|
.parent()
|
||||||
|
.filter(|parent| !parent.as_os_str().is_empty())
|
||||||
|
.unwrap_or_else(|| Path::new("."));
|
||||||
|
let mut temp = tempfile::Builder::new()
|
||||||
|
.prefix(".ventoy-")
|
||||||
|
.tempfile_in(parent)?;
|
||||||
|
let mut file = temp.as_file_mut();
|
||||||
file.set_len(size)?;
|
file.set_len(size)?;
|
||||||
|
|
||||||
// Write boot code
|
// Write boot code
|
||||||
@@ -64,6 +71,8 @@ impl VentoyImage {
|
|||||||
format_exfat(&mut file, layout.data_offset(), layout.data_size(), label)?;
|
format_exfat(&mut file, layout.data_offset(), layout.data_size(), label)?;
|
||||||
|
|
||||||
file.flush()?;
|
file.flush()?;
|
||||||
|
temp.persist(path)
|
||||||
|
.map_err(|error| VentoyError::Io(error.error))?;
|
||||||
|
|
||||||
println!("[INFO] Ventoy IMG created successfully!");
|
println!("[INFO] Ventoy IMG created successfully!");
|
||||||
|
|
||||||
@@ -274,3 +283,20 @@ impl VentoyImage {
|
|||||||
&self.path
|
&self.path
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn failed_create_does_not_publish_partial_image() {
|
||||||
|
let dir = tempfile::tempdir().unwrap();
|
||||||
|
let path = dir.path().join("ventoy.img");
|
||||||
|
|
||||||
|
let error = VentoyImage::create(&path, "64M", "TEST").err().unwrap();
|
||||||
|
|
||||||
|
assert!(matches!(error, VentoyError::ResourceNotFound(_)));
|
||||||
|
assert!(!path.exists());
|
||||||
|
assert_eq!(std::fs::read_dir(dir.path()).unwrap().count(), 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -56,7 +56,7 @@ impl VentoyDrive {
|
|||||||
info!("Creating {} MB Ventoy drive at {}", size_mb, path.display());
|
info!("Creating {} MB Ventoy drive at {}", size_mb, path.display());
|
||||||
|
|
||||||
let info = tokio::task::spawn_blocking(move || {
|
let info = tokio::task::spawn_blocking(move || {
|
||||||
VentoyImage::create(&path, &size_str, DEFAULT_LABEL).map_err(ventoy_to_app_error)?;
|
VentoyImage::create(&path, &size_str, DEFAULT_LABEL).map_err(drive_init_error)?;
|
||||||
|
|
||||||
let metadata = std::fs::metadata(&path)
|
let metadata = std::fs::metadata(&path)
|
||||||
.map_err(|e| AppError::Internal(format!("Failed to read drive metadata: {}", e)))?;
|
.map_err(|e| AppError::Internal(format!("Failed to read drive metadata: {}", e)))?;
|
||||||
@@ -354,6 +354,30 @@ fn ventoy_to_app_error(err: VentoyError) -> AppError {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn drive_init_error(err: VentoyError) -> AppError {
|
||||||
|
let VentoyError::Io(error) = err else {
|
||||||
|
return ventoy_to_app_error(err);
|
||||||
|
};
|
||||||
|
|
||||||
|
#[cfg(unix)]
|
||||||
|
match error.raw_os_error() {
|
||||||
|
Some(libc::EFBIG) => AppError::BadRequest(
|
||||||
|
"MSD directory filesystem does not support a virtual drive file of this size".into(),
|
||||||
|
),
|
||||||
|
Some(libc::ENOSPC) => AppError::BadRequest(
|
||||||
|
"MSD directory does not have enough free space for the virtual drive".into(),
|
||||||
|
),
|
||||||
|
Some(libc::EROFS) => AppError::BadRequest("MSD directory filesystem is read-only".into()),
|
||||||
|
Some(libc::EACCES | libc::EPERM) => AppError::BadRequest(
|
||||||
|
"One-KVM does not have permission to write to the MSD directory".into(),
|
||||||
|
),
|
||||||
|
_ => AppError::Io(error),
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(not(unix))]
|
||||||
|
AppError::Io(error)
|
||||||
|
}
|
||||||
|
|
||||||
fn ventoy_file_to_drive_file(info: VentoyFileInfo, parent_path: &str) -> DriveFile {
|
fn ventoy_file_to_drive_file(info: VentoyFileInfo, parent_path: &str) -> DriveFile {
|
||||||
let full_path = if parent_path.is_empty() || parent_path == "/" {
|
let full_path = if parent_path.is_empty() || parent_path == "/" {
|
||||||
format!("/{}", info.name)
|
format!("/{}", info.name)
|
||||||
@@ -436,12 +460,26 @@ impl Drop for ChannelWriter {
|
|||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
use crate::error::AppError;
|
||||||
use std::process::Command;
|
use std::process::Command;
|
||||||
use std::sync::OnceLock;
|
use std::sync::OnceLock;
|
||||||
use tempfile::TempDir;
|
use tempfile::TempDir;
|
||||||
|
|
||||||
static RESOURCE_DIR: &str = concat!(env!("CARGO_MANIFEST_DIR"), "/../ventoy-img-rs/resources");
|
static RESOURCE_DIR: &str = concat!(env!("CARGO_MANIFEST_DIR"), "/../ventoy-img-rs/resources");
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn classifies_drive_creation_io_errors() {
|
||||||
|
for (errno, expected) in [
|
||||||
|
(libc::EFBIG, "does not support"),
|
||||||
|
(libc::ENOSPC, "enough free space"),
|
||||||
|
(libc::EROFS, "read-only"),
|
||||||
|
(libc::EACCES, "permission"),
|
||||||
|
] {
|
||||||
|
let error = drive_init_error(VentoyError::Io(std::io::Error::from_raw_os_error(errno)));
|
||||||
|
assert!(matches!(error, AppError::BadRequest(message) if message.contains(expected)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn init_ventoy_resources() -> bool {
|
fn init_ventoy_resources() -> bool {
|
||||||
static INIT: OnceLock<bool> = OnceLock::new();
|
static INIT: OnceLock<bool> = OnceLock::new();
|
||||||
*INIT.get_or_init(|| {
|
*INIT.get_or_init(|| {
|
||||||
|
|||||||
@@ -469,7 +469,16 @@ async function createDrive() {
|
|||||||
showDriveInitDialog.value = false
|
showDriveInitDialog.value = false
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error('Failed to initialize drive:', e)
|
console.error('Failed to initialize drive:', e)
|
||||||
toast.error(t('msd.driveCreateFailed'))
|
let description: string | undefined
|
||||||
|
if (e instanceof ApiError) {
|
||||||
|
const message = e.message
|
||||||
|
if (message.includes('does not support a virtual drive file')) description = t('msd.driveFileTooLarge')
|
||||||
|
else if (message.includes('does not have enough free space')) description = t('msd.driveSpaceUnavailable')
|
||||||
|
else if (message.includes('filesystem is read-only')) description = t('msd.driveReadOnly')
|
||||||
|
else if (message.includes('permission to write')) description = t('msd.drivePermissionDenied')
|
||||||
|
else description = message
|
||||||
|
}
|
||||||
|
toast.error(t('msd.driveCreateFailed'), { description })
|
||||||
} finally {
|
} finally {
|
||||||
initializingDrive.value = false
|
initializingDrive.value = false
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -396,7 +396,10 @@ export default {
|
|||||||
driveSpaceUnknown: 'Unable to read available space for the MSD directory filesystem',
|
driveSpaceUnknown: 'Unable to read available space for the MSD directory filesystem',
|
||||||
driveSpaceUnavailable: 'Not enough space on the MSD directory filesystem to create a virtual drive',
|
driveSpaceUnavailable: 'Not enough space on the MSD directory filesystem to create a virtual drive',
|
||||||
driveSpaceTooSmall: 'Available space is less than {min} MB; cannot create a virtual drive',
|
driveSpaceTooSmall: 'Available space is less than {min} MB; cannot create a virtual drive',
|
||||||
driveCreateFailed: 'Failed to create virtual drive. Check available space for the MSD directory',
|
driveCreateFailed: 'Failed to create virtual drive',
|
||||||
|
driveFileTooLarge: 'The MSD directory filesystem does not support a virtual drive file this large',
|
||||||
|
driveReadOnly: 'The MSD directory filesystem is read-only',
|
||||||
|
drivePermissionDenied: 'One-KVM does not have permission to write to the MSD directory',
|
||||||
driveConnectedBlocked: 'Disconnect the drive before editing files',
|
driveConnectedBlocked: 'Disconnect the drive before editing files',
|
||||||
driveConnectedFilesHidden: 'Drive is connected to target machine — file list unavailable',
|
driveConnectedFilesHidden: 'Drive is connected to target machine — file list unavailable',
|
||||||
uploadFailed: 'File upload failed',
|
uploadFailed: 'File upload failed',
|
||||||
|
|||||||
@@ -395,7 +395,10 @@ export default {
|
|||||||
driveSpaceUnknown: '无法读取 MSD 目录所在储存空间的剩余可用空间',
|
driveSpaceUnknown: '无法读取 MSD 目录所在储存空间的剩余可用空间',
|
||||||
driveSpaceUnavailable: 'MSD 目录所在储存空间不足,无法创建虚拟U盘',
|
driveSpaceUnavailable: 'MSD 目录所在储存空间不足,无法创建虚拟U盘',
|
||||||
driveSpaceTooSmall: '剩余可用空间不足 {min} MB,无法创建虚拟U盘',
|
driveSpaceTooSmall: '剩余可用空间不足 {min} MB,无法创建虚拟U盘',
|
||||||
driveCreateFailed: '虚拟U盘创建失败,请检查 MSD 目录剩余空间',
|
driveCreateFailed: '虚拟U盘创建失败',
|
||||||
|
driveFileTooLarge: 'MSD 目录所在文件系统不支持这么大的单个虚拟盘文件',
|
||||||
|
driveReadOnly: 'MSD 目录所在文件系统为只读',
|
||||||
|
drivePermissionDenied: 'One-KVM 没有写入 MSD 目录的权限',
|
||||||
driveConnectedBlocked: '请先断开连接,再操作文件',
|
driveConnectedBlocked: '请先断开连接,再操作文件',
|
||||||
driveConnectedFilesHidden: '已连接到被控机,文件列表暂不可用',
|
driveConnectedFilesHidden: '已连接到被控机,文件列表暂不可用',
|
||||||
uploadFailed: '文件上传失败',
|
uploadFailed: '文件上传失败',
|
||||||
|
|||||||
Reference in New Issue
Block a user