From 653cbcf30b70877551d5f83d90c185ff7bb50bab Mon Sep 17 00:00:00 2001 From: LoGin Date: Tue, 3 Mar 2026 13:47:40 +0800 Subject: [PATCH] feat: add virtiofs support (#1800) - Add virtio-fs device driver with PCI transport support - Implement virtio-fs bridge for FUSE communication - Extend FUSE connection to support virtio-fs backend - Add virtiofs mountable filesystem with kernel thread bridge - Update Makefile with virtiofs build and run targets - Add virtiofsd startup script and configuration - Upgrade virtio-drivers dependency to support virtio-fs Signed-off-by: longjin --- Makefile | 12 + kernel/Cargo.lock | 2 +- kernel/Cargo.toml | 2 +- kernel/src/driver/virtio/mod.rs | 1 + kernel/src/driver/virtio/virtio.rs | 5 +- kernel/src/driver/virtio/virtio_fs.rs | 231 +++++++ kernel/src/filesystem/fuse/conn.rs | 36 +- kernel/src/filesystem/fuse/mod.rs | 1 + kernel/src/filesystem/fuse/virtiofs.rs | 873 +++++++++++++++++++++++++ tools/run-qemu.sh | 51 ++ tools/virtiofs/.gitignore | 1 + tools/virtiofs/env.sh.example | 19 + tools/virtiofs/start_virtiofsd.sh | 103 +++ 13 files changed, 1331 insertions(+), 6 deletions(-) create mode 100644 kernel/src/driver/virtio/virtio_fs.rs create mode 100644 kernel/src/filesystem/fuse/virtiofs.rs create mode 100644 tools/virtiofs/.gitignore create mode 100644 tools/virtiofs/env.sh.example create mode 100755 tools/virtiofs/start_virtiofsd.sh diff --git a/Makefile b/Makefile index 732905c03..88b0023bc 100644 --- a/Makefile +++ b/Makefile @@ -205,6 +205,16 @@ else sh -c "cd tools && bash run-qemu.sh --bios=legacy --display=nographic && cd .." endif +# 启动virtiofsd(用于virtiofs功能验证) +.PHONY: virtiofsd +virtiofsd: + sh -c "cd tools/virtiofs && bash start_virtiofsd.sh && cd ../.." + +# 不编译,直接以nographic方式启动QEMU,并启用virtiofs设备 +.PHONY: qemu-virtiofs-nographic +qemu-virtiofs-nographic: check_arch + sh -c "cd tools && DRAGONOS_VIRTIOFS_ENABLE=1 bash run-qemu.sh --bios=legacy --display=nographic && cd .." + # 不编译,直接启动QEMU(UEFI) qemu-uefi: check_arch sh -c "cd tools && bash run-qemu.sh --bios=uefi --display=window && cd .." @@ -339,7 +349,9 @@ help: @echo "" @echo "运行:" @echo " make qemu - 不编译,直接从已有的磁盘镜像启动运行" + @echo " make qemu-virtiofs-nographic - 不编译,以nographic方式启动并启用virtiofs设备" @echo " make qemu-uefi - 不编译,直接从已有的磁盘镜像以UEFI启动运行" + @echo " make virtiofsd - 启动virtiofsd(读取tools/virtiofs/env.sh)" @echo "" @echo "" @echo "注: 对于上述的run, run-uefi, qemu, qemu-uefi命令可以在命令后加上-vnc后缀,来通过vnc连接到DragonOS, 默认会在5900端口运行vnc服务器。如:make run-vnc " diff --git a/kernel/Cargo.lock b/kernel/Cargo.lock index f83bfd308..a48740a44 100644 --- a/kernel/Cargo.lock +++ b/kernel/Cargo.lock @@ -1815,7 +1815,7 @@ checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" [[package]] name = "virtio-drivers" version = "0.7.2" -source = "git+https://git.mirrors.dragonos.org.cn/DragonOS-Community/virtio-drivers?rev=415ab38ff9#415ab38ff99f3c8e150269c04f65d684ba9d1365" +source = "git+https://git.mirrors.dragonos.org.cn/DragonOS-Community/virtio-drivers?rev=755e305#755e3053cd8856279e44abd0ecca4d6eef3aec93" dependencies = [ "bitflags 2.9.1", "log", diff --git a/kernel/Cargo.toml b/kernel/Cargo.toml index c1261aef0..0c8a28c8a 100644 --- a/kernel/Cargo.toml +++ b/kernel/Cargo.toml @@ -81,7 +81,7 @@ smoltcp = { version = "=0.12.0", git = "https://git.mirrors.dragonos.org.cn/Drag syscall_table_macros = { path = "crates/syscall_table_macros" } system_error = { path = "crates/system_error" } unified-init = { path = "crates/unified-init" } -virtio-drivers = { git = "https://git.mirrors.dragonos.org.cn/DragonOS-Community/virtio-drivers", rev = "415ab38ff9" } +virtio-drivers = { git = "https://git.mirrors.dragonos.org.cn/DragonOS-Community/virtio-drivers", rev = "755e305" } wait_queue_macros = { path = "crates/wait_queue_macros" } paste = "=1.0.14" slabmalloc = { path = "crates/rust-slabmalloc" } diff --git a/kernel/src/driver/virtio/mod.rs b/kernel/src/driver/virtio/mod.rs index fca5212ba..d0a796a3b 100644 --- a/kernel/src/driver/virtio/mod.rs +++ b/kernel/src/driver/virtio/mod.rs @@ -13,6 +13,7 @@ pub mod transport_mmio; pub mod transport_pci; #[allow(clippy::module_inception)] pub mod virtio; +pub mod virtio_fs; pub mod virtio_impl; /// virtio 设备厂商ID diff --git a/kernel/src/driver/virtio/virtio.rs b/kernel/src/driver/virtio/virtio.rs index 0490f3af6..22944a817 100644 --- a/kernel/src/driver/virtio/virtio.rs +++ b/kernel/src/driver/virtio/virtio.rs @@ -1,5 +1,6 @@ use super::mmio::virtio_probe_mmio; use super::transport_pci::PciTransport; +use super::virtio_fs::virtio_fs; use super::virtio_impl::HalImpl; use crate::driver::base::device::bus::Bus; use crate::driver::base::device::{Device, DeviceId}; @@ -76,6 +77,7 @@ pub(super) fn virtio_device_init( warn!("Not support virtio_input device for now"); } DeviceType::Network => virtio_net(transport, dev_id, dev_parent), + DeviceType::FileSystem => virtio_fs(transport, dev_id, dev_parent), t => { warn!("Unrecognized virtio device: {:?}", t); } @@ -102,7 +104,8 @@ fn virtio_device_search() -> Vec> { let standard_device = device.as_standard_device().unwrap(); let header = &standard_device.common_header; // log::info!("header: {:?}", header); - if header.device_id >= 0x1000 && header.device_id <= 0x103F { + // 支持 transitional(0x1000..=0x103f) 与 modern(0x1040..=0x107f) virtio PCI 设备。 + if header.device_id >= 0x1000 && header.device_id <= 0x107F { virtio_list.push(standard_device); } } diff --git a/kernel/src/driver/virtio/virtio_fs.rs b/kernel/src/driver/virtio/virtio_fs.rs new file mode 100644 index 000000000..b2779133b --- /dev/null +++ b/kernel/src/driver/virtio/virtio_fs.rs @@ -0,0 +1,231 @@ +use alloc::{ + collections::BTreeMap, + string::{String, ToString}, + sync::Arc, +}; +use core::ptr; + +use log::{info, warn}; +use system_error::SystemError; +use virtio_drivers::transport::Transport; + +use crate::{ + driver::base::device::{Device, DeviceId}, + libs::{spinlock::SpinLock, wait_queue::WaitQueue}, +}; + +use super::transport::VirtIOTransport; + +const VIRTIO_FS_TAG_LEN: usize = 36; +const VIRTIO_FS_REQUEST_QUEUE_BASE: u16 = 1; +const VIRTIO_FS_MAX_REQUEST_QUEUES: u32 = + (u16::MAX as u32) - (VIRTIO_FS_REQUEST_QUEUE_BASE as u32) + 1; + +#[repr(C, packed)] +struct VirtioFsConfig { + tag: [u8; VIRTIO_FS_TAG_LEN], + num_request_queues: u32, +} + +struct VirtioFsTransportHolder(VirtIOTransport); + +impl core::fmt::Debug for VirtioFsTransportHolder { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + f.write_str("VirtioFsTransportHolder(..)") + } +} + +// Safety: virtio-fs transport is always moved by ownership under SpinLock +// protection, and never accessed concurrently through shared references. +unsafe impl Send for VirtioFsTransportHolder {} + +#[derive(Debug)] +struct VirtioFsInstanceState { + transport: Option, + session_active: bool, + active_session_id: u64, + released_session_id: u64, + next_session_id: u64, +} + +#[derive(Debug)] +pub struct VirtioFsInstance { + tag: String, + num_request_queues: u32, + dev_id: Arc, + state: SpinLock, + session_wait: WaitQueue, +} + +impl VirtioFsInstance { + fn new( + tag: String, + num_request_queues: u32, + dev_id: Arc, + transport: VirtIOTransport, + ) -> Self { + Self { + tag, + num_request_queues, + dev_id, + state: SpinLock::new(VirtioFsInstanceState { + transport: Some(VirtioFsTransportHolder(transport)), + session_active: false, + active_session_id: 0, + released_session_id: 0, + next_session_id: 1, + }), + session_wait: WaitQueue::default(), + } + } + + pub fn tag(&self) -> &str { + &self.tag + } + + pub fn dev_id(&self) -> &Arc { + &self.dev_id + } + + pub fn num_request_queues(&self) -> u32 { + self.num_request_queues + } + + pub fn hiprio_queue_index(&self) -> u16 { + 0 + } + + pub fn request_queue_count(&self) -> usize { + self.num_request_queues as usize + } + + pub fn request_queue_index_by_slot(&self, slot: usize) -> Option { + if slot >= self.request_queue_count() { + return None; + } + let slot = u16::try_from(slot).ok()?; + VIRTIO_FS_REQUEST_QUEUE_BASE + .checked_add(slot) + .filter(|idx| { + (*idx as usize) + < (VIRTIO_FS_REQUEST_QUEUE_BASE as usize + self.request_queue_count()) + }) + } + + pub fn take_transport_for_session(&self) -> Result<(VirtIOTransport, u64), SystemError> { + let mut state = self.state.lock_irqsave(); + if state.session_active { + return Err(SystemError::EBUSY); + } + + let transport = state.transport.take().ok_or(SystemError::EBUSY)?.0; + let session_id = state.next_session_id; + state.next_session_id = state.next_session_id.wrapping_add(1); + state.active_session_id = session_id; + state.session_active = true; + Ok((transport, session_id)) + } + + pub fn put_transport_after_session(&self, transport: VirtIOTransport) { + let mut state = self.state.lock_irqsave(); + state.transport = Some(VirtioFsTransportHolder(transport)); + state.released_session_id = state.active_session_id; + state.active_session_id = 0; + state.session_active = false; + drop(state); + self.session_wait.wakeup(None); + } + + pub fn wait_session_released(&self, session_id: u64) { + self.session_wait.wait_until(|| { + let state = self.state.lock_irqsave(); + if state.released_session_id == session_id && state.transport.is_some() { + Some(()) + } else { + None + } + }); + } +} + +lazy_static! { + static ref VIRTIO_FS_INSTANCES: SpinLock>> = + SpinLock::new(BTreeMap::new()); +} + +fn read_config(transport: &VirtIOTransport) -> Result<(String, u32), SystemError> { + let cfg = transport + .config_space::() + .map_err(|_| SystemError::EINVAL)?; + + let base = cfg.as_ptr() as *const u8; + let mut tag_raw = [0u8; VIRTIO_FS_TAG_LEN]; + for (i, b) in tag_raw.iter_mut().enumerate() { + *b = unsafe { ptr::read_volatile(base.add(i)) }; + } + + let tag_len = tag_raw + .iter() + .position(|x| *x == 0) + .unwrap_or(VIRTIO_FS_TAG_LEN); + if tag_len == 0 { + return Err(SystemError::EINVAL); + } + + let tag = core::str::from_utf8(&tag_raw[..tag_len]) + .map_err(|_| SystemError::EINVAL)? + .to_string(); + let mut nrqs_raw = [0u8; core::mem::size_of::()]; + for (i, b) in nrqs_raw.iter_mut().enumerate() { + *b = unsafe { ptr::read_volatile(base.add(VIRTIO_FS_TAG_LEN + i)) }; + } + let nrqs = u32::from_le_bytes(nrqs_raw); + if nrqs == 0 || nrqs > VIRTIO_FS_MAX_REQUEST_QUEUES { + return Err(SystemError::EINVAL); + } + + Ok((tag, nrqs)) +} + +pub fn virtio_fs_find_instance(tag: &str) -> Option> { + VIRTIO_FS_INSTANCES.lock_irqsave().get(tag).cloned() +} + +pub fn virtio_fs( + transport: VirtIOTransport, + dev_id: Arc, + _dev_parent: Option>, +) { + let (tag, nrqs) = match read_config(&transport) { + Ok(v) => v, + Err(e) => { + warn!( + "virtio-fs: failed to read config for device {:?}: {:?}", + dev_id, e + ); + return; + } + }; + + let instance = Arc::new(VirtioFsInstance::new( + tag.clone(), + nrqs, + dev_id.clone(), + transport, + )); + + let mut map = VIRTIO_FS_INSTANCES.lock_irqsave(); + if map.contains_key(&tag) { + warn!( + "virtio-fs: duplicated tag '{}' for device {:?}, ignore new device", + tag, dev_id + ); + return; + } + + map.insert(tag.clone(), instance); + info!( + "virtio-fs: registered instance tag='{}' dev={:?} request_queues={}", + tag, dev_id, nrqs + ); +} diff --git a/kernel/src/filesystem/fuse/conn.rs b/kernel/src/filesystem/fuse/conn.rs index ed7046c8d..3d6a3df3b 100644 --- a/kernel/src/filesystem/fuse/conn.rs +++ b/kernel/src/filesystem/fuse/conn.rs @@ -147,6 +147,7 @@ struct FuseConnInner { no_open: bool, no_opendir: bool, no_readdirplus: bool, + max_write_cap: usize, pending: VecDeque>, processing: BTreeMap>, } @@ -165,8 +166,23 @@ pub struct FuseConn { impl FuseConn { // Keep this in sync with `sys_read.rs` userspace chunking size. const USER_READ_CHUNK: usize = 64 * 1024; + const MIN_MAX_WRITE: usize = 4096; pub fn new() -> Arc { + Self::new_with_max_write_cap(Self::max_write_cap_for_user_read_chunk()) + } + + pub fn new_for_virtiofs(max_message_size: usize) -> Arc { + let overhead = core::mem::size_of::() + core::mem::size_of::(); + let cap = if max_message_size > overhead { + core::cmp::max(Self::MIN_MAX_WRITE, max_message_size - overhead) + } else { + Self::MIN_MAX_WRITE + }; + Self::new_with_max_write_cap(cap) + } + + fn new_with_max_write_cap(max_write_cap: usize) -> Arc { Arc::new(Self { inner: Mutex::new(FuseConnInner { connected: true, @@ -179,6 +195,7 @@ impl FuseConn { no_open: false, no_opendir: false, no_readdirplus: false, + max_write_cap, pending: VecDeque::new(), processing: BTreeMap::new(), }), @@ -196,6 +213,14 @@ impl FuseConn { self.inner.lock().mounted } + pub fn is_connected(&self) -> bool { + self.inner.lock().connected + } + + pub fn has_pending_requests(&self) -> bool { + !self.inner.lock().pending.is_empty() + } + pub fn mark_mounted(&self) -> Result<(), SystemError> { let mut g = self.inner.lock(); if !g.connected { @@ -461,12 +486,17 @@ impl FuseConn { fn max_write_cap_for_user_read_chunk() -> usize { let overhead = core::mem::size_of::() + core::mem::size_of::(); if Self::USER_READ_CHUNK <= overhead { - 4096 + Self::MIN_MAX_WRITE } else { Self::USER_READ_CHUNK - overhead } } + fn max_write_cap(&self) -> usize { + let g = self.inner.lock(); + core::cmp::max(Self::MIN_MAX_WRITE, g.max_write_cap) + } + fn min_read_buffer(&self) -> usize { let g = self.inner.lock(); Self::calc_min_read_buffer(g.init.max_write as usize) @@ -777,11 +807,11 @@ impl FuseConn { 1 }; let negotiated_max_write = core::cmp::max(4096usize, init_out.max_write as usize); - let max_write_cap = Self::max_write_cap_for_user_read_chunk(); + let max_write_cap = self.max_write_cap(); let capped_max_write = core::cmp::min(negotiated_max_write, max_write_cap); if capped_max_write < negotiated_max_write { log::trace!( - "fuse: cap negotiated max_write from {} to {} due user read chunk limit", + "fuse: cap negotiated max_write from {} to {} due backend read buffer limit", negotiated_max_write, capped_max_write ); diff --git a/kernel/src/filesystem/fuse/mod.rs b/kernel/src/filesystem/fuse/mod.rs index 26e7dc358..d41de1f47 100644 --- a/kernel/src/filesystem/fuse/mod.rs +++ b/kernel/src/filesystem/fuse/mod.rs @@ -4,3 +4,4 @@ pub mod fs; pub mod inode; pub mod private_data; pub mod protocol; +pub mod virtiofs; diff --git a/kernel/src/filesystem/fuse/virtiofs.rs b/kernel/src/filesystem/fuse/virtiofs.rs new file mode 100644 index 000000000..06213996b --- /dev/null +++ b/kernel/src/filesystem/fuse/virtiofs.rs @@ -0,0 +1,873 @@ +use alloc::{ + boxed::Box, + collections::{BTreeMap, VecDeque}, + string::String, + sync::Arc, + vec, + vec::Vec, +}; + +use linkme::distributed_slice; +use log::{debug, warn}; +use system_error::SystemError; +use virtio_drivers::{ + queue::VirtQueue, + transport::{DeviceStatus, Transport}, + Error as VirtioError, PAGE_SIZE, +}; + +use crate::{ + driver::virtio::{ + transport::VirtIOTransport, + virtio_drivers_error_to_system_error, + virtio_fs::{virtio_fs_find_instance, VirtioFsInstance}, + virtio_impl::HalImpl, + }, + filesystem::vfs::{ + FileSystem, FileSystemMakerData, FsInfo, IndexNode, MountableFileSystem, SuperBlock, + FSMAKER, + }, + process::{kthread::KernelThreadClosure, kthread::KernelThreadMechanism, ProcessManager}, + register_mountable_fs, + time::{sleep::nanosleep, PosixTimeSpec}, +}; + +use super::{ + conn::FuseConn, + fs::{FuseFS, FuseMountData}, + protocol::{ + fuse_pack_struct, fuse_read_struct, FuseInHeader, FuseOutHeader, FUSE_DESTROY, FUSE_FORGET, + FUSE_INTERRUPT, + }, +}; + +const VIRTIOFS_REQ_QUEUE_SIZE: usize = 8; +const VIRTIOFS_REQ_BUF_SIZE: usize = 256 * 1024; +const VIRTIOFS_RSP_BUF_SIZE: usize = 256 * 1024; +const VIRTIOFS_POLL_NS: i64 = 1_000_000; +const VIRTIOFS_PUMP_BUDGET: usize = 64; + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +enum QueueKind { + Hiprio, + Request(usize), +} + +#[derive(Debug)] +struct PendingReq { + req: Vec, + unique: u64, + opcode: u32, + noreply: bool, + queue: QueueKind, +} + +#[derive(Debug)] +struct InflightReq { + pending: PendingReq, + rsp: Option>, +} + +struct VirtioFsBridgeContext { + instance: Arc, + conn: Arc, + transport: Option, + hiprio_vq: Option>, + request_vqs: Vec>, + hiprio_pending: VecDeque, + request_pending: Vec>, + hiprio_inflight: BTreeMap, + request_inflight: Vec>, + next_request_slot: usize, + req_buf: Vec, +} + +impl VirtioFsBridgeContext { + fn poll_pause() { + let _ = nanosleep(PosixTimeSpec::new(0, VIRTIOFS_POLL_NS)); + } + + fn has_internal_pending(&self) -> bool { + !self.hiprio_pending.is_empty() || self.request_pending.iter().any(|q| !q.is_empty()) + } + + fn has_inflight(&self) -> bool { + !self.hiprio_inflight.is_empty() || self.request_inflight.iter().any(|m| !m.is_empty()) + } + + fn push_pending_back(&mut self, pending: PendingReq) -> Result<(), SystemError> { + match pending.queue { + QueueKind::Hiprio => self.hiprio_pending.push_back(pending), + QueueKind::Request(slot) => self + .request_pending + .get_mut(slot) + .ok_or(SystemError::EINVAL)? + .push_back(pending), + } + Ok(()) + } + + fn push_pending_front(&mut self, pending: PendingReq) -> Result<(), SystemError> { + match pending.queue { + QueueKind::Hiprio => self.hiprio_pending.push_front(pending), + QueueKind::Request(slot) => self + .request_pending + .get_mut(slot) + .ok_or(SystemError::EINVAL)? + .push_front(pending), + } + Ok(()) + } + + fn pop_pending_front(&mut self, kind: QueueKind) -> Result, SystemError> { + Ok(match kind { + QueueKind::Hiprio => self.hiprio_pending.pop_front(), + QueueKind::Request(slot) => self + .request_pending + .get_mut(slot) + .ok_or(SystemError::EINVAL)? + .pop_front(), + }) + } + + fn queue_index(&self, kind: QueueKind) -> Result { + match kind { + QueueKind::Hiprio => Ok(self.instance.hiprio_queue_index()), + QueueKind::Request(slot) => self + .instance + .request_queue_index_by_slot(slot) + .ok_or(SystemError::EINVAL), + } + } + + fn take_inflight(&mut self, kind: QueueKind, token: u16) -> Result { + match kind { + QueueKind::Hiprio => self.hiprio_inflight.remove(&token).ok_or(SystemError::EIO), + QueueKind::Request(slot) => self + .request_inflight + .get_mut(slot) + .ok_or(SystemError::EINVAL)? + .remove(&token) + .ok_or(SystemError::EIO), + } + } + + fn put_back_inflight( + &mut self, + kind: QueueKind, + token: u16, + inflight: InflightReq, + ) -> Result<(), SystemError> { + let replaced = match kind { + QueueKind::Hiprio => self.hiprio_inflight.insert(token, inflight), + QueueKind::Request(slot) => self + .request_inflight + .get_mut(slot) + .ok_or(SystemError::EINVAL)? + .insert(token, inflight), + }; + debug_assert!(replaced.is_none()); + Ok(()) + } + + fn complete_request_with_errno(conn: &Arc, unique: u64, errno: i32) { + if unique == 0 { + return; + } + let out_hdr = FuseOutHeader { + len: core::mem::size_of::() as u32, + error: errno, + unique, + }; + let payload = fuse_pack_struct(&out_hdr); + let _ = conn.write_reply(payload); + } + + fn complete_request_with_error(&self, unique: u64, err: SystemError) { + Self::complete_request_with_errno(&self.conn, unique, err.to_posix_errno()); + } + + fn choose_request_slot(&mut self) -> Result { + if self.request_vqs.is_empty() { + return Err(SystemError::ENODEV); + } + let slot = self.next_request_slot % self.request_vqs.len(); + self.next_request_slot = (self.next_request_slot + 1) % self.request_vqs.len(); + Ok(slot) + } + + fn pump_new_requests(&mut self) -> Result { + let mut progressed = false; + for _ in 0..VIRTIOFS_PUMP_BUDGET { + let len = match self.conn.read_request(true, &mut self.req_buf) { + Ok(len) => len, + Err(SystemError::EAGAIN_OR_EWOULDBLOCK) => break, + Err(e) => return Err(e), + }; + let req = self.req_buf[..len].to_vec(); + let in_hdr: FuseInHeader = fuse_read_struct(&req)?; + let noreply = matches!(in_hdr.opcode, FUSE_FORGET | FUSE_DESTROY); + let queue = if matches!(in_hdr.opcode, FUSE_FORGET | FUSE_INTERRUPT) { + QueueKind::Hiprio + } else { + QueueKind::Request(self.choose_request_slot()?) + }; + self.push_pending_back(PendingReq { + req, + unique: in_hdr.unique, + opcode: in_hdr.opcode, + noreply, + queue, + })?; + progressed = true; + } + Ok(progressed) + } + + fn submit_one_pending(&mut self, kind: QueueKind) -> Result { + let pending = match self.pop_pending_front(kind)? { + Some(p) => p, + None => return Ok(false), + }; + let queue_idx = self.queue_index(kind)?; + let mut rsp = if pending.noreply { + None + } else { + Some(vec![0u8; VIRTIOFS_RSP_BUF_SIZE]) + }; + + let (token, should_notify) = match kind { + QueueKind::Hiprio => { + let queue = self.hiprio_vq.as_mut().ok_or(SystemError::EIO)?; + let token = if let Some(rsp_buf) = rsp.as_mut() { + let inputs = [pending.req.as_slice()]; + let mut outputs = [rsp_buf.as_mut_slice()]; + unsafe { queue.add(&inputs, &mut outputs) } + } else { + let inputs = [pending.req.as_slice()]; + let mut outputs: [&mut [u8]; 0] = []; + unsafe { queue.add(&inputs, &mut outputs) } + }; + (token, queue.should_notify()) + } + QueueKind::Request(slot) => { + let queue = self.request_vqs.get_mut(slot).ok_or(SystemError::EINVAL)?; + let token = if let Some(rsp_buf) = rsp.as_mut() { + let inputs = [pending.req.as_slice()]; + let mut outputs = [rsp_buf.as_mut_slice()]; + unsafe { queue.add(&inputs, &mut outputs) } + } else { + let inputs = [pending.req.as_slice()]; + let mut outputs: [&mut [u8]; 0] = []; + unsafe { queue.add(&inputs, &mut outputs) } + }; + (token, queue.should_notify()) + } + }; + + let token = match token { + Ok(token) => token, + Err(VirtioError::QueueFull) | Err(VirtioError::NotReady) => { + self.push_pending_front(pending)?; + return Ok(false); + } + Err(e) => { + let se = virtio_drivers_error_to_system_error(e); + warn!( + "virtiofs bridge: submit failed opcode={} unique={} queue={:?} err={:?}", + pending.opcode, pending.unique, kind, se + ); + if !pending.noreply { + self.complete_request_with_error(pending.unique, se); + } + return Ok(true); + } + }; + + if should_notify { + self.transport + .as_mut() + .ok_or(SystemError::EIO)? + .notify(queue_idx); + } + + let inflight = InflightReq { pending, rsp }; + match kind { + QueueKind::Hiprio => { + self.hiprio_inflight.insert(token, inflight); + } + QueueKind::Request(slot) => { + self.request_inflight + .get_mut(slot) + .ok_or(SystemError::EINVAL)? + .insert(token, inflight); + } + } + Ok(true) + } + + fn submit_pending(&mut self) -> Result { + let mut progressed = false; + while self.submit_one_pending(QueueKind::Hiprio)? { + progressed = true; + } + + for slot in 0..self.request_vqs.len() { + while self.submit_one_pending(QueueKind::Request(slot))? { + progressed = true; + } + } + Ok(progressed) + } + + fn pop_one_used(&mut self, kind: QueueKind) -> Result { + let token = match kind { + QueueKind::Hiprio => { + let queue = self.hiprio_vq.as_mut().ok_or(SystemError::EIO)?; + if !queue.can_pop() { + return Ok(false); + } + queue.peek_used().ok_or(SystemError::EIO)? + } + QueueKind::Request(slot) => { + let queue = self.request_vqs.get_mut(slot).ok_or(SystemError::EINVAL)?; + if !queue.can_pop() { + return Ok(false); + } + queue.peek_used().ok_or(SystemError::EIO)? + } + }; + + let mut inflight = self.take_inflight(kind, token)?; + + let used_len_res = match kind { + QueueKind::Hiprio => { + let queue = self.hiprio_vq.as_mut().ok_or(SystemError::EIO)?; + let inputs = [inflight.pending.req.as_slice()]; + if let Some(rsp_buf) = inflight.rsp.as_mut() { + let mut outputs = [rsp_buf.as_mut_slice()]; + unsafe { queue.pop_used(token, &inputs, &mut outputs) } + .map_err(virtio_drivers_error_to_system_error) + } else { + let mut outputs: [&mut [u8]; 0] = []; + unsafe { queue.pop_used(token, &inputs, &mut outputs) } + .map_err(virtio_drivers_error_to_system_error) + } + } + QueueKind::Request(slot) => { + let queue = self.request_vqs.get_mut(slot).ok_or(SystemError::EINVAL)?; + let inputs = [inflight.pending.req.as_slice()]; + if let Some(rsp_buf) = inflight.rsp.as_mut() { + let mut outputs = [rsp_buf.as_mut_slice()]; + unsafe { queue.pop_used(token, &inputs, &mut outputs) } + .map_err(virtio_drivers_error_to_system_error) + } else { + let mut outputs: [&mut [u8]; 0] = []; + unsafe { queue.pop_used(token, &inputs, &mut outputs) } + .map_err(virtio_drivers_error_to_system_error) + } + } + }; + + let used_len = match used_len_res { + Ok(v) => v as usize, + Err(e) => { + let unique = inflight.pending.unique; + self.put_back_inflight(kind, token, inflight)?; + warn!( + "virtiofs bridge: pop_used failed unique={} token={} queue={:?} err={:?}", + unique, token, kind, e + ); + return Err(e); + } + }; + + if inflight.pending.noreply { + return Ok(true); + } + + let rsp_buf = inflight.rsp.as_ref().ok_or(SystemError::EIO)?; + if used_len > rsp_buf.len() { + self.complete_request_with_error(inflight.pending.unique, SystemError::EIO); + return Ok(true); + } + + match self.conn.write_reply(&rsp_buf[..used_len]) { + Ok(_) | Err(SystemError::ENOENT) => {} + Err(e) => { + // Linux virtio-fs always ends a completed request from the used ring. + // Keep that behavior here: fail this unique instead of exiting bridge loop. + let unique = inflight.pending.unique; + let completion_err = if e == SystemError::EINVAL { + SystemError::EIO + } else { + e.clone() + }; + warn!( + "virtiofs bridge: write_reply failed unique={} opcode={} err={:?}, complete with {:?}", + unique, inflight.pending.opcode, e, completion_err + ); + self.complete_request_with_error(unique, completion_err); + } + } + Ok(true) + } + + fn drain_completions(&mut self) -> Result { + let mut progressed = false; + while self.pop_one_used(QueueKind::Hiprio)? { + progressed = true; + } + + for slot in 0..self.request_vqs.len() { + while self.pop_one_used(QueueKind::Request(slot))? { + progressed = true; + } + } + + Ok(progressed) + } + + fn fail_all_unfinished(&mut self, err: SystemError) { + let conn = self.conn.clone(); + let errno = err.to_posix_errno(); + let mut need_reply = Vec::new(); + + while let Some(req) = self.hiprio_pending.pop_front() { + if !req.noreply { + need_reply.push(req.unique); + } + } + + for pending_q in &mut self.request_pending { + while let Some(req) = pending_q.pop_front() { + if !req.noreply { + need_reply.push(req.unique); + } + } + } + + for (_, req) in self.hiprio_inflight.iter() { + if !req.pending.noreply { + need_reply.push(req.pending.unique); + } + } + self.hiprio_inflight.clear(); + + for inflight_map in &mut self.request_inflight { + for (_, req) in inflight_map.iter() { + if !req.pending.noreply { + need_reply.push(req.pending.unique); + } + } + inflight_map.clear(); + } + + for unique in need_reply { + Self::complete_request_with_errno(&conn, unique, errno); + } + } + + fn run_loop(&mut self) -> Result<(), SystemError> { + loop { + let mut progressed = false; + + match self.pump_new_requests() { + Ok(v) => progressed |= v, + Err(SystemError::ENOTCONN) => {} + Err(e) => { + warn!("virtiofs bridge: read_request failed: {:?}", e); + if !self.conn.is_connected() { + break; + } + } + } + + progressed |= self.submit_pending()?; + + if let Some(transport) = self.transport.as_mut() { + let _ = transport.ack_interrupt(); + } + progressed |= self.drain_completions()?; + + if !self.conn.is_connected() && !self.has_inflight() { + break; + } + + if !self.conn.is_mounted() + && !self.conn.has_pending_requests() + && !self.has_internal_pending() + && !self.has_inflight() + { + break; + } + + if !progressed { + Self::poll_pause(); + } + } + + Ok(()) + } + + fn finish(&mut self) { + self.fail_all_unfinished(SystemError::ENOTCONN); + + if let Some(transport) = self.transport.as_mut() { + if self.hiprio_vq.take().is_some() { + transport.queue_unset(self.instance.hiprio_queue_index()); + } + for slot in 0..self.request_vqs.len() { + if let Some(idx) = self.instance.request_queue_index_by_slot(slot) { + transport.queue_unset(idx); + } + } + self.request_vqs.clear(); + transport.set_status(DeviceStatus::empty()); + } + + if let Some(transport) = self.transport.take() { + self.instance.put_transport_after_session(transport); + } + } +} + +fn virtiofs_bridge_thread_entry(arg: usize) -> i32 { + let mut ctx = unsafe { Box::from_raw(arg as *mut VirtioFsBridgeContext) }; + let result = ctx.run_loop(); + if let Err(e) = &result { + warn!("virtiofs bridge thread exit with error: {:?}", e); + } + ctx.finish(); + result.map(|_| 0).unwrap_or_else(|e| e.to_posix_errno()) +} + +fn start_bridge(instance: Arc, conn: Arc) -> Result { + let (mut transport, session_id) = instance.take_transport_for_session()?; + if instance.request_queue_count() == 0 { + warn!( + "virtiofs bridge: no request queues: tag='{}' dev={:?}", + instance.tag(), + instance.dev_id(), + ); + instance.put_transport_after_session(transport); + return Err(SystemError::EINVAL); + } + + debug!( + "virtiofs bridge: start tag='{}' dev={:?} request_queues={}", + instance.tag(), + instance.dev_id(), + instance.num_request_queues() + ); + + transport.set_status(DeviceStatus::empty()); + transport.set_status(DeviceStatus::ACKNOWLEDGE | DeviceStatus::DRIVER); + let _device_features = transport.read_device_features(); + transport.write_driver_features(0); + transport + .set_status(DeviceStatus::ACKNOWLEDGE | DeviceStatus::DRIVER | DeviceStatus::FEATURES_OK); + let status = transport.get_status(); + if !status.contains(DeviceStatus::FEATURES_OK) { + warn!( + "virtiofs bridge: device rejected features tag='{}' dev={:?} status={:?}", + instance.tag(), + instance.dev_id(), + status + ); + transport.set_status(DeviceStatus::FAILED); + instance.put_transport_after_session(transport); + return Err(SystemError::ENODEV); + } + transport.set_guest_page_size(PAGE_SIZE as u32); + + let hiprio_vq = match VirtQueue::::new( + &mut transport, + instance.hiprio_queue_index(), + false, + false, + ) { + Ok(vq) => vq, + Err(e) => { + let se = virtio_drivers_error_to_system_error(e); + transport.set_status(DeviceStatus::FAILED); + instance.put_transport_after_session(transport); + return Err(se); + } + }; + + let mut request_vqs = Vec::with_capacity(instance.request_queue_count()); + for slot in 0..instance.request_queue_count() { + let idx = instance + .request_queue_index_by_slot(slot) + .ok_or(SystemError::EINVAL)?; + let vq = match VirtQueue::::new( + &mut transport, + idx, + false, + false, + ) { + Ok(vq) => vq, + Err(e) => { + let se = virtio_drivers_error_to_system_error(e); + transport.set_status(DeviceStatus::FAILED); + instance.put_transport_after_session(transport); + return Err(se); + } + }; + request_vqs.push(vq); + } + transport.finish_init(); + + let ctx = Box::new(VirtioFsBridgeContext { + instance, + conn, + transport: Some(transport), + hiprio_vq: Some(hiprio_vq), + request_pending: core::iter::repeat_with(VecDeque::new) + .take(request_vqs.len()) + .collect(), + request_inflight: core::iter::repeat_with(BTreeMap::new) + .take(request_vqs.len()) + .collect(), + request_vqs, + hiprio_pending: VecDeque::new(), + hiprio_inflight: BTreeMap::new(), + next_request_slot: 0, + req_buf: vec![0u8; VIRTIOFS_REQ_BUF_SIZE], + }); + + let raw = Box::into_raw(ctx); + if KernelThreadMechanism::create_and_run( + KernelThreadClosure::StaticUsizeClosure(( + &(virtiofs_bridge_thread_entry as fn(usize) -> i32), + raw as usize, + )), + String::from("virtiofs-bridge"), + ) + .is_none() + { + let mut ctx = unsafe { Box::from_raw(raw) }; + ctx.finish(); + return Err(SystemError::ENOMEM); + } + + Ok(session_id) +} + +#[derive(Debug)] +struct VirtioFsMountData { + rootmode: u32, + user_id: u32, + group_id: u32, + allow_other: bool, + default_permissions: bool, + dax_mode: VirtioFsDaxMode, + conn: Arc, + instance: Arc, +} + +impl FileSystemMakerData for VirtioFsMountData { + fn as_any(&self) -> &dyn core::any::Any { + self + } +} + +#[derive(Debug)] +struct VirtioFsFs { + inner: Arc, + instance: Arc, + session_id: u64, +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +enum VirtioFsDaxMode { + Never, + Always, + Inode, +} + +impl VirtioFsFs { + fn parse_opt_u32_decimal(v: &str) -> Result { + v.parse::().map_err(|_| SystemError::EINVAL) + } + + fn parse_opt_u32_octal(v: &str) -> Result { + u32::from_str_radix(v, 8).map_err(|_| SystemError::EINVAL) + } + + fn parse_opt_bool_switch(v: &str) -> bool { + v.is_empty() || v != "0" + } + + fn parse_dax_mode(v: &str) -> Result { + if v.is_empty() { + return Ok(VirtioFsDaxMode::Always); + } + + match v { + "always" => Ok(VirtioFsDaxMode::Always), + "never" => Ok(VirtioFsDaxMode::Never), + "inode" => Ok(VirtioFsDaxMode::Inode), + _ => Err(SystemError::EINVAL), + } + } + + fn parse_mount_options( + raw: Option<&str>, + ) -> Result<(u32, u32, u32, bool, bool, VirtioFsDaxMode), SystemError> { + let pcb = ProcessManager::current_pcb(); + let cred = pcb.cred(); + + let mut rootmode: Option = None; + let mut user_id: Option = None; + let mut group_id: Option = None; + let mut default_permissions = true; + let mut allow_other = true; + let mut dax_mode = VirtioFsDaxMode::Never; + + for part in raw.unwrap_or("").split(',') { + let part = part.trim(); + if part.is_empty() { + continue; + } + let (k, v) = match part.split_once('=') { + Some((k, v)) => (k.trim(), v.trim()), + None => (part, ""), + }; + + match k { + "rootmode" => rootmode = Some(Self::parse_opt_u32_octal(v)?), + "user_id" => user_id = Some(Self::parse_opt_u32_decimal(v)?), + "group_id" => group_id = Some(Self::parse_opt_u32_decimal(v)?), + "default_permissions" => default_permissions = Self::parse_opt_bool_switch(v), + "allow_other" => allow_other = Self::parse_opt_bool_switch(v), + "dax" => dax_mode = Self::parse_dax_mode(v)?, + _ => return Err(SystemError::EINVAL), + } + } + + if dax_mode != VirtioFsDaxMode::Never { + return Err(SystemError::EOPNOTSUPP_OR_ENOTSUP); + } + + Ok(( + rootmode.unwrap_or(0o040755), + user_id.unwrap_or(cred.fsuid.data() as u32), + group_id.unwrap_or(cred.fsgid.data() as u32), + default_permissions, + allow_other, + dax_mode, + )) + } +} + +impl FileSystem for VirtioFsFs { + fn root_inode(&self) -> Arc { + self.inner.root_inode() + } + + fn info(&self) -> FsInfo { + self.inner.info() + } + + fn support_readahead(&self) -> bool { + self.inner.support_readahead() + } + + fn as_any_ref(&self) -> &dyn core::any::Any { + self + } + + fn name(&self) -> &str { + "virtiofs" + } + + fn super_block(&self) -> SuperBlock { + self.inner.super_block() + } + + fn statfs(&self, inode: &Arc) -> Result { + self.inner.statfs(inode) + } + + fn permission_policy(&self) -> crate::filesystem::vfs::FsPermissionPolicy { + self.inner.permission_policy() + } + + fn on_umount(&self) { + self.inner.on_umount(); + self.instance.wait_session_released(self.session_id); + } +} + +impl MountableFileSystem for VirtioFsFs { + fn make_mount_data( + raw_data: Option<&str>, + source: &str, + ) -> Result>, SystemError> { + if source.is_empty() { + return Err(SystemError::EINVAL); + } + + let (rootmode, user_id, group_id, default_permissions, allow_other, dax_mode) = + Self::parse_mount_options(raw_data)?; + let instance = virtio_fs_find_instance(source).ok_or(SystemError::ENODEV)?; + let conn = FuseConn::new_for_virtiofs(core::cmp::min( + VIRTIOFS_REQ_BUF_SIZE, + VIRTIOFS_RSP_BUF_SIZE, + )); + + Ok(Some(Arc::new(VirtioFsMountData { + rootmode, + user_id, + group_id, + allow_other, + default_permissions, + dax_mode, + conn, + instance, + }))) + } + + fn make_fs( + data: Option<&dyn FileSystemMakerData>, + ) -> Result, SystemError> { + let md = data + .and_then(|d| d.as_any().downcast_ref::()) + .ok_or(SystemError::EINVAL)?; + + let fuse_mount_data = FuseMountData { + rootmode: md.rootmode, + user_id: md.user_id, + group_id: md.group_id, + allow_other: md.allow_other, + default_permissions: md.default_permissions, + conn: md.conn.clone(), + }; + + if md.dax_mode != VirtioFsDaxMode::Never { + return Err(SystemError::EOPNOTSUPP_OR_ENOTSUP); + } + + let inner = ::make_fs(Some( + &fuse_mount_data as &dyn FileSystemMakerData, + ))?; + + let session_id = match start_bridge(md.instance.clone(), md.conn.clone()) { + Ok(id) => id, + Err(e) => { + inner.on_umount(); + return Err(e); + } + }; + + Ok(Arc::new(VirtioFsFs { + inner, + instance: md.instance.clone(), + session_id, + })) + } +} + +register_mountable_fs!(VirtioFsFs, VIRTIOFSMAKER, "virtiofs"); diff --git a/tools/run-qemu.sh b/tools/run-qemu.sh index 80446eb5c..14fd22264 100755 --- a/tools/run-qemu.sh +++ b/tools/run-qemu.sh @@ -10,6 +10,10 @@ # # - AUTO_TEST: 自动测试选项 # - SYSCALL_TEST_DIR: 系统调用测试目录 +# - DRAGONOS_VIRTIOFS_ENABLE: 是否启用 virtiofs(1启用,默认0) +# - DRAGONOS_VIRTIOFS_SOCKET: virtiofsd socket路径 +# - DRAGONOS_VIRTIOFS_TAG: virtiofs 挂载tag +# - DRAGONOS_VIRTIOFS_ENV_FILE: virtiofs配置文件路径(默认 ${ROOT_PATH}/tools/virtiofs/env.sh) # check_dependencies() @@ -99,6 +103,10 @@ QEMU_DISK_IMAGE="../bin/${DISK_NAME}" QEMU_EXT4_DISK_IMAGE="../bin/${EXT4_DISK_NAME}" QEMU_FAT_DISK_IMAGE="../bin/${FAT_DISK_NAME}" QEMU_MEMORY="2G" +DRAGONOS_VIRTIOFS_ENABLE=${DRAGONOS_VIRTIOFS_ENABLE:=0} +DRAGONOS_VIRTIOFS_SOCKET=${DRAGONOS_VIRTIOFS_SOCKET:=/tmp/dragonos-virtiofsd.sock} +DRAGONOS_VIRTIOFS_TAG=${DRAGONOS_VIRTIOFS_TAG:=hostshare} +DRAGONOS_VIRTIOFS_ENV_FILE=${DRAGONOS_VIRTIOFS_ENV_FILE:=} # 检查必要的环境变量 if [ -z "${ROOT_PATH}" ]; then @@ -142,6 +150,9 @@ QEMU_DRIVE_ARGS=(-drive "id=disk,file=${QEMU_DISK_IMAGE},if=none,format=raw") QEMU_ACCEL_ARGS=() QEMU_DEVICE_ARGS=() QEMU_DISPLAY_ARGS=() +QEMU_OBJECT_ARGS=() +QEMU_NUMA_ARGS=() +QEMU_CHARDEV_ARGS=() QEMU_ARGS=() # QEMU_ARGUMENT+=" -S " @@ -294,6 +305,43 @@ setup_kernel_init_program # 从环境变量设置内核命令行参数 setup_kernel_cmdline_from_env +if [ "${DRAGONOS_VIRTIOFS_ENABLE}" == "1" ]; then + if [ "${ARCH}" != "x86_64" ]; then + echo "[错误] virtiofs临时运行支持当前仅实现x86_64" + exit 1 + fi + + if [ -z "${DRAGONOS_VIRTIOFS_ENV_FILE}" ]; then + DRAGONOS_VIRTIOFS_ENV_FILE="${ROOT_PATH}/tools/virtiofs/env.sh" + fi + + if [ -f "${DRAGONOS_VIRTIOFS_ENV_FILE}" ]; then + # shellcheck source=/dev/null + . "${DRAGONOS_VIRTIOFS_ENV_FILE}" + if [ "${DRAGONOS_VIRTIOFS_SOCKET}" = "/tmp/dragonos-virtiofsd.sock" ] && [ -n "${SOCKET_PATH:-}" ]; then + DRAGONOS_VIRTIOFS_SOCKET="${SOCKET_PATH}" + fi + if [ "${DRAGONOS_VIRTIOFS_TAG}" = "hostshare" ] && [ -n "${VIRTIOFS_TAG:-}" ]; then + DRAGONOS_VIRTIOFS_TAG="${VIRTIOFS_TAG}" + fi + fi + + if [ ! -S "${DRAGONOS_VIRTIOFS_SOCKET}" ]; then + echo "[错误] 未检测到virtiofsd socket: ${DRAGONOS_VIRTIOFS_SOCKET}" + echo "[提示] 请先在另一个终端启动: tools/virtiofs/start_virtiofsd.sh" + exit 1 + fi + + echo "[INFO] 启用virtiofs: tag=${DRAGONOS_VIRTIOFS_TAG}, socket=${DRAGONOS_VIRTIOFS_SOCKET}" + + QEMU_OBJECT_ARGS+=( + -object "memory-backend-memfd,id=mem,size=${QEMU_MEMORY},share=on" + ) + QEMU_NUMA_ARGS+=(-numa "node,memdev=mem") + QEMU_CHARDEV_ARGS+=(-chardev "socket,id=char_virtiofs,path=${DRAGONOS_VIRTIOFS_SOCKET}") + QEMU_DEVICE_ARGS+=(-device "vhost-user-fs-pci,chardev=char_virtiofs,tag=${DRAGONOS_VIRTIOFS_TAG}") +fi + if [ ${QEMU_NOGRAPHIC} == true ]; then QEMU_SERIAL_ARGS=(-serial chardev:mux -monitor chardev:mux -chardev "stdio,id=mux,mux=on,signal=off,logfile=${QEMU_SERIAL_LOG_FILE}") @@ -349,6 +397,9 @@ QEMU_ARGS+=( "${QEMU_MACHINE_ARGS[@]}" "${QEMU_CPU_ARGS[@]}" "${QEMU_RTC_ARGS[@]}" + "${QEMU_OBJECT_ARGS[@]}" + "${QEMU_NUMA_ARGS[@]}" + "${QEMU_CHARDEV_ARGS[@]}" "${QEMU_SERIAL_ARGS[@]}" "${QEMU_DRIVE_ARGS[@]}" "${QEMU_DEVICE_ARGS[@]}" diff --git a/tools/virtiofs/.gitignore b/tools/virtiofs/.gitignore new file mode 100644 index 000000000..137e67833 --- /dev/null +++ b/tools/virtiofs/.gitignore @@ -0,0 +1 @@ +env.sh diff --git a/tools/virtiofs/env.sh.example b/tools/virtiofs/env.sh.example new file mode 100644 index 000000000..46751a395 --- /dev/null +++ b/tools/virtiofs/env.sh.example @@ -0,0 +1,19 @@ +#!/usr/bin/env bash +# +# 复制为 env.sh 后按需修改: +# cp env.sh.example env.sh + +# Host 上要共享给 guest 的目录 +export HOST_SHARE_DIR="${HOME}/dragonos-virtiofs-share" + +# Guest 内 mount -t virtiofs 使用的 tag +export VIRTIOFS_TAG="hostshare" + +# virtiofsd 运行时目录与 socket +export RUNTIME_DIR="${HOME}/.dragonos-virtiofs" +export SOCKET_PATH="${RUNTIME_DIR}/virtiofsd.sock" + +# 可选:手动指定 virtiofsd 可执行文件,留空则自动探测 +export VIRTIOFSD_BIN="" +export VIRTIOFSD_CACHE="auto" +export VIRTIOFSD_EXTRA_ARGS="" diff --git a/tools/virtiofs/start_virtiofsd.sh b/tools/virtiofs/start_virtiofsd.sh new file mode 100755 index 000000000..f1d936df3 --- /dev/null +++ b/tools/virtiofs/start_virtiofsd.sh @@ -0,0 +1,103 @@ +#!/usr/bin/env bash +set -euo pipefail + +SCRIPT_DIR="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)" +SCRIPT_PATH="${SCRIPT_DIR}/$(basename -- "${BASH_SOURCE[0]}")" +ENV_FILE="${SCRIPT_DIR}/env.sh" + +if [[ "${EUID}" -ne 0 ]]; then + echo "virtiofsd 需要以 sudo 权限启动,正在尝试提权..." + exec sudo HOME="${HOME}" bash "${SCRIPT_PATH}" "$@" +fi + +if [[ ! -f "${ENV_FILE}" ]]; then + echo "未找到 ${ENV_FILE}" + echo "请先执行:cp \"${SCRIPT_DIR}/env.sh.example\" \"${ENV_FILE}\" 并按需修改" + exit 1 +fi + +# shellcheck source=/dev/null +source "${ENV_FILE}" + +detect_virtiofsd_bin() { + if [[ -n "${VIRTIOFSD_BIN:-}" ]]; then + echo "${VIRTIOFSD_BIN}" + return 0 + fi + + if command -v virtiofsd >/dev/null 2>&1; then + command -v virtiofsd + return 0 + fi + + local candidates=( + "/usr/libexec/virtiofsd" + "/usr/lib/qemu/virtiofsd" + ) + local p + for p in "${candidates[@]}"; do + if [[ -x "${p}" ]]; then + echo "${p}" + return 0 + fi + done + + return 1 +} + +VIRTIOFSD_PATH="$(detect_virtiofsd_bin || true)" +if [[ -z "${VIRTIOFSD_PATH}" ]]; then + echo "找不到 virtiofsd,请安装 qemu/virtiofsd 或在 env.sh 中设置 VIRTIOFSD_BIN" + exit 1 +fi + +mkdir -p "${HOST_SHARE_DIR}" +mkdir -p "${RUNTIME_DIR}" +rm -f "${SOCKET_PATH}" + +echo "启动 virtiofsd:" +echo " binary : ${VIRTIOFSD_PATH}" +echo " shared : ${HOST_SHARE_DIR}" +echo " socket : ${SOCKET_PATH}" +echo " cache : ${VIRTIOFSD_CACHE:-auto}" +echo +echo "保持此终端运行,不要关闭。" + +build_virtiofsd_args() { + local help_text + help_text="$("${VIRTIOFSD_PATH}" --help 2>&1 || true)" + + VIRTIOFSD_ARGS=("--socket-path=${SOCKET_PATH}") + + if grep -q -- "--shared-dir" <<<"${help_text}"; then + VIRTIOFSD_ARGS+=("--shared-dir=${HOST_SHARE_DIR}") + if grep -q -- "--cache" <<<"${help_text}"; then + VIRTIOFSD_ARGS+=("--cache=${VIRTIOFSD_CACHE:-auto}") + else + VIRTIOFSD_ARGS+=("-o" "cache=${VIRTIOFSD_CACHE:-auto}") + fi + return 0 + fi + + if grep -q "source=PATH" <<<"${help_text}"; then + VIRTIOFSD_ARGS+=( + "-o" "source=${HOST_SHARE_DIR}" + "-o" "cache=${VIRTIOFSD_CACHE:-auto}" + ) + return 0 + fi + + VIRTIOFSD_ARGS+=( + "-o" "cache=${VIRTIOFSD_CACHE:-auto}" + "${HOST_SHARE_DIR}" + ) +} + +build_virtiofsd_args + +if [[ -n "${VIRTIOFSD_EXTRA_ARGS:-}" ]]; then + # shellcheck disable=SC2086 + exec "${VIRTIOFSD_PATH}" "${VIRTIOFSD_ARGS[@]}" ${VIRTIOFSD_EXTRA_ARGS} +else + exec "${VIRTIOFSD_PATH}" "${VIRTIOFSD_ARGS[@]}" +fi