Files
One-KVM/src/otg/configfs.rs
mofeng-git 887f29096f feat: 增加 OTG HID 远程唤醒和 OTG MSD 设备名称配置
- 在补丁内核上启用 HID 写入唤醒及 USB 远程唤醒描述符
  - 支持全局配置 Flash 和 CD-ROM INQUIRY 字符串
  - 兼容普通内核的通用 INQUIRY 属性
  - 调整 OTG 功能布局并更新大容量 DVD 镜像提示
2026-07-29 13:56:52 +08:00

171 lines
4.7 KiB
Rust

use std::fs::{self, File, OpenOptions};
use std::io::Write;
use std::path::Path;
use std::process::Command;
use crate::error::{AppError, Result};
pub const CONFIGFS_PATH: &str = "/sys/kernel/config/usb_gadget";
pub const DEFAULT_GADGET_NAME: &str = "one-kvm";
pub const DEFAULT_USB_VENDOR_ID: u16 = 0x1d6b;
pub const DEFAULT_USB_PRODUCT_ID: u16 = 0x0104;
pub const DEFAULT_USB_BCD_DEVICE: u16 = 0x0100;
pub const USB_BCD_USB: u16 = 0x0200;
pub fn is_configfs_available() -> bool {
configfs_path().exists()
}
pub fn configfs_path() -> &'static Path {
Path::new(CONFIGFS_PATH)
}
/// Loads `libcomposite` if needed; does not mount configfs.
pub fn ensure_libcomposite_loaded() -> Result<()> {
if is_configfs_available() {
return Ok(());
}
if !Path::new("/sys/module/libcomposite").exists() {
let status = Command::new("modprobe")
.arg("libcomposite")
.status()
.map_err(|e| {
AppError::Internal(format!("Failed to run modprobe libcomposite: {}", e))
})?;
if !status.success() {
return Err(AppError::Internal(format!(
"modprobe libcomposite failed with status {}",
status
)));
}
}
if is_configfs_available() {
Ok(())
} else {
Err(AppError::Internal(
"libcomposite is not available after modprobe; check configfs mount and kernel support"
.to_string(),
))
}
}
pub fn find_udc() -> Option<String> {
list_udcs().into_iter().next()
}
pub fn list_udcs() -> Vec<String> {
let mut devices = Vec::new();
collect_dir_names(Path::new("/sys/class/udc"), &mut devices);
devices.sort();
devices.dedup();
devices
}
fn collect_dir_names(path: &Path, devices: &mut Vec<String>) {
if let Ok(entries) = fs::read_dir(path) {
for entry in entries.flatten() {
let name = entry.file_name().to_string_lossy().trim().to_string();
if !name.is_empty() {
devices.push(name);
}
}
}
}
/// Sysfs/configfs: one write syscall with final buffer (incl. newline when needed).
pub fn write_file(path: &Path, content: &str) -> Result<()> {
let mut file = OpenOptions::new()
.write(true)
.open(path)
.or_else(|e| {
if path.exists() {
Err(e)
} else {
File::create(path)
}
})
.map_err(|e| AppError::Internal(format!("Failed to open {}: {}", path.display(), e)))?;
let data: std::borrow::Cow<[u8]> = if content.ends_with('\n') {
content.as_bytes().into()
} else {
let mut buf = content.as_bytes().to_vec();
buf.push(b'\n');
buf.into()
};
file.write_all(&data)
.map_err(|e| AppError::Internal(format!("Failed to write to {}: {}", path.display(), e)))?;
file.flush()
.map_err(|e| AppError::Internal(format!("Failed to flush {}: {}", path.display(), e)))?;
Ok(())
}
/// Write an optional configfs/sysfs attribute when the running kernel exposes it.
/// This keeps newer kernel enhancements compatible with older kernels.
pub fn write_file_if_exists(path: &Path, content: &str) -> Result<bool> {
if !path.exists() {
return Ok(false);
}
write_file(path, content)?;
Ok(true)
}
pub fn write_bytes(path: &Path, data: &[u8]) -> Result<()> {
let mut file = File::create(path)
.map_err(|e| AppError::Internal(format!("Failed to create {}: {}", path.display(), e)))?;
file.write_all(data)
.map_err(|e| AppError::Internal(format!("Failed to write to {}: {}", path.display(), e)))?;
Ok(())
}
pub fn create_dir(path: &Path) -> Result<()> {
fs::create_dir_all(path).map_err(|e| {
AppError::Internal(format!(
"Failed to create directory {}: {}",
path.display(),
e
))
})
}
pub fn remove_dir(path: &Path) -> Result<()> {
if path.exists() {
fs::remove_dir(path).map_err(|e| {
AppError::Internal(format!(
"Failed to remove directory {}: {}",
path.display(),
e
))
})?;
}
Ok(())
}
pub fn remove_file(path: &Path) -> Result<()> {
if path.exists() {
fs::remove_file(path).map_err(|e| {
AppError::Internal(format!("Failed to remove file {}: {}", path.display(), e))
})?;
}
Ok(())
}
pub fn create_symlink(src: &Path, dest: &Path) -> Result<()> {
std::os::unix::fs::symlink(src, dest).map_err(|e| {
AppError::Internal(format!(
"Failed to create symlink {} -> {}: {}",
dest.display(),
src.display(),
e
))
})
}