Files
One-KVM/src/otg/configfs.rs

161 lines
4.4 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(())
}
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
))
})
}