This is an automated email from the ASF dual-hosted git repository.
xuanwo pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/opendal.git
The following commit(s) were added to refs/heads/main by this push:
new 43d53886f7 feat(ovfs): add lookup and unit tests (#4997)
43d53886f7 is described below
commit 43d53886f786668b2797e9e3b6b4066a454612a8
Author: zjregee <[email protected]>
AuthorDate: Tue Aug 13 15:29:09 2024 +0800
feat(ovfs): add lookup and unit tests (#4997)
* feat: add lookup
* feat: add tests for reader and writer
* typo
* typo
* fix cargo fmt check
---
integrations/virtiofs/src/filesystem.rs | 66 ++++++++-
integrations/virtiofs/src/filesystem_message.rs | 20 +++
integrations/virtiofs/src/virtiofs_util.rs | 178 ++++++++++++++++++++++++
3 files changed, 261 insertions(+), 3 deletions(-)
diff --git a/integrations/virtiofs/src/filesystem.rs
b/integrations/virtiofs/src/filesystem.rs
index 4fb38b15dc..a9fe6a8f37 100644
--- a/integrations/virtiofs/src/filesystem.rs
+++ b/integrations/virtiofs/src/filesystem.rs
@@ -15,8 +15,11 @@
// specific language governing permissions and limitations
// under the License.
+use std::collections::HashMap;
+use std::io::Read;
use std::io::Write;
use std::mem::size_of;
+use std::sync::Mutex;
use std::time::Duration;
use log::debug;
@@ -51,6 +54,7 @@ enum FileType {
File,
}
+#[derive(Clone)]
struct OpenedFile {
path: String,
metadata: Attr,
@@ -113,6 +117,9 @@ pub struct Filesystem {
uid: u32,
gid: u32,
opened_files: Slab<OpenedFile>,
+ // Since we need to manually manage the allocation of inodes,
+ // we record the inode of each opened file here.
+ opened_files_map: Mutex<HashMap<String, u64>>,
}
impl Filesystem {
@@ -130,6 +137,7 @@ impl Filesystem {
uid: 1000,
gid: 1000,
opened_files: Slab::new(),
+ opened_files_map: Mutex::new(HashMap::new()),
}
}
@@ -145,6 +153,7 @@ impl Filesystem {
match opcode {
Opcode::Init => self.init(in_header, r, w),
Opcode::Destroy => self.destroy(in_header, r, w),
+ Opcode::Lookup => self.lookup(in_header, r, w),
Opcode::Getattr => self.getattr(in_header, r, w),
Opcode::Setattr => self.setattr(in_header, r, w),
}
@@ -227,6 +236,46 @@ impl Filesystem {
Ok(0)
}
+ fn lookup(&self, in_header: InHeader, mut r: Reader, w: Writer) ->
Result<usize> {
+ let name_len = in_header.len as usize - size_of::<InHeader>();
+ let mut buf = vec![0u8; name_len];
+ r.read_exact(&mut buf).map_err(|e| {
+ new_unexpected_error("failed to decode protocol messages",
Some(e.into()))
+ })?;
+ let name = String::from_utf8(buf).map_err(|e| {
+ new_unexpected_error("failed to decode protocol messages",
Some(e.into()))
+ })?;
+
+ debug!("lookup: parent inode={} name={}", in_header.nodeid, name);
+
+ let parent_path = match self
+ .opened_files
+ .get(in_header.nodeid as usize)
+ .map(|f| f.path.clone())
+ {
+ Some(path) => path,
+ None => return Filesystem::reply_error(in_header.unique, w),
+ };
+
+ let path = format!("{}/{}", parent_path, name);
+ let metadata = match self.rt.block_on(self.do_get_metadata(&path)) {
+ Ok(metadata) => metadata,
+ Err(_) => return Filesystem::reply_error(in_header.unique, w),
+ };
+
+ let out = EntryOut {
+ nodeid: metadata.metadata.ino,
+ entry_valid: DEFAULT_TTL.as_secs(),
+ attr_valid: DEFAULT_TTL.as_secs(),
+ entry_valid_nsec: DEFAULT_TTL.subsec_nanos(),
+ attr_valid_nsec: DEFAULT_TTL.subsec_nanos(),
+ attr: metadata.metadata,
+ ..Default::default()
+ };
+
+ Filesystem::reply_ok(Some(out), None, in_header.unique, w)
+ }
+
fn getattr(&self, in_header: InHeader, _r: Reader, w: Writer) ->
Result<usize> {
debug!("getattr: inode={}", in_header.nodeid);
@@ -239,11 +288,10 @@ impl Filesystem {
None => return Filesystem::reply_error(in_header.unique, w),
};
- let mut metadata = match self.rt.block_on(self.do_get_metadata(&path))
{
+ let metadata = match self.rt.block_on(self.do_get_metadata(&path)) {
Ok(metadata) => metadata,
Err(_) => return Filesystem::reply_error(in_header.unique, w),
};
- metadata.metadata.ino = in_header.nodeid;
let out = AttrOut {
attr_valid: DEFAULT_TTL.as_secs(),
@@ -265,7 +313,19 @@ impl Filesystem {
impl Filesystem {
async fn do_get_metadata(&self, path: &str) -> Result<OpenedFile> {
let metadata =
self.core.stat(path).await.map_err(opendal_error2error)?;
- let attr = opendal_metadata2opened_file(path, &metadata, self.uid,
self.gid);
+ let mut attr = opendal_metadata2opened_file(path, &metadata, self.uid,
self.gid);
+
+ let mut opened_files_map = self.opened_files_map.lock().unwrap();
+ if let Some(inode) = opened_files_map.get(path) {
+ attr.metadata.ino = *inode;
+ } else {
+ let inode = self
+ .opened_files
+ .insert(attr.clone())
+ .expect("failed to allocate inode");
+ attr.metadata.ino = inode as u64;
+ opened_files_map.insert(path.to_string(), inode as u64);
+ }
Ok(attr)
}
diff --git a/integrations/virtiofs/src/filesystem_message.rs
b/integrations/virtiofs/src/filesystem_message.rs
index dd924507e4..c26d33fbc3 100644
--- a/integrations/virtiofs/src/filesystem_message.rs
+++ b/integrations/virtiofs/src/filesystem_message.rs
@@ -23,6 +23,7 @@ use crate::error::*;
/// The corresponding value needs to be aligned with the specification.
#[non_exhaustive]
pub enum Opcode {
+ Lookup = 1,
Getattr = 3,
Setattr = 4,
Init = 26,
@@ -34,6 +35,7 @@ impl TryFrom<u32> for Opcode {
fn try_from(value: u32) -> Result<Self, Self::Error> {
match value {
+ 1 => Ok(Opcode::Lookup),
3 => Ok(Opcode::Getattr),
4 => Ok(Opcode::Setattr),
26 => Ok(Opcode::Init),
@@ -137,6 +139,23 @@ pub struct InitOut {
pub unused: [u32; 7],
}
+/// EntryOut is used to return the file entry in the filesystem call.
+///
+/// The fields of the struct need to conform to the specific format of the
virtiofs message.
+/// Currently, we only need to align them exactly with virtiofsd.
+/// Reference:
https://gitlab.com/virtio-fs/virtiofsd/-/blob/main/src/fuse.rs?ref_type=heads#L737
+#[repr(C)]
+#[derive(Debug, Default, Clone, Copy)]
+pub struct EntryOut {
+ pub nodeid: u64,
+ pub generation: u64,
+ pub entry_valid: u64,
+ pub attr_valid: u64,
+ pub entry_valid_nsec: u32,
+ pub attr_valid_nsec: u32,
+ pub attr: Attr,
+}
+
/// AttrOut is used to return the file attributes in the filesystem call.
///
/// The fields of the struct need to conform to the specific format of the
virtiofs message.
@@ -158,4 +177,5 @@ unsafe impl ByteValued for InHeader {}
unsafe impl ByteValued for OutHeader {}
unsafe impl ByteValued for InitIn {}
unsafe impl ByteValued for InitOut {}
+unsafe impl ByteValued for EntryOut {}
unsafe impl ByteValued for AttrOut {}
diff --git a/integrations/virtiofs/src/virtiofs_util.rs
b/integrations/virtiofs/src/virtiofs_util.rs
index bcbdf6ea91..ea531c6907 100644
--- a/integrations/virtiofs/src/virtiofs_util.rs
+++ b/integrations/virtiofs/src/virtiofs_util.rs
@@ -45,6 +45,11 @@ struct DescriptorChainConsumer<'a, B> {
}
impl<'a, B: BitmapSlice> DescriptorChainConsumer<'a, B> {
+ #[cfg(test)]
+ fn available_bytes(&self) -> usize {
+ self.buffers.iter().fold(0, |count, vs| count + vs.len())
+ }
+
fn bytes_consumed(&self) -> usize {
self.bytes_consumed
}
@@ -146,6 +151,16 @@ impl<'a, B: Bitmap + BitmapSlice + 'static> Reader<'a, B> {
self.read_exact(buf)?;
Ok(unsafe { obj.assume_init() })
}
+
+ #[cfg(test)]
+ pub fn available_bytes(&self) -> usize {
+ self.buffer.available_bytes()
+ }
+
+ #[cfg(test)]
+ pub fn bytes_read(&self) -> usize {
+ self.buffer.bytes_consumed()
+ }
}
impl<'a, B: BitmapSlice> io::Read for Reader<'a, B> {
@@ -216,6 +231,11 @@ impl<'a, B: Bitmap + BitmapSlice + 'static> Writer<'a, B> {
})
}
+ #[cfg(test)]
+ pub fn available_bytes(&self) -> usize {
+ self.buffer.available_bytes()
+ }
+
pub fn bytes_written(&self) -> usize {
self.buffer.bytes_consumed()
}
@@ -245,3 +265,161 @@ impl<'a, B: BitmapSlice> Write for Writer<'a, B> {
Ok(())
}
}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+ use virtio_queue::Queue;
+ use virtio_queue::QueueOwnedT;
+ use virtio_queue::QueueT;
+ use vm_memory::Bytes;
+ use vm_memory::GuestAddress;
+ use vm_memory::Le16;
+ use vm_memory::Le32;
+ use vm_memory::Le64;
+
+ const VIRTQ_DESC_F_NEXT: u16 = 0x1;
+ const VIRTQ_DESC_F_WRITE: u16 = 0x2;
+
+ enum DescriptorType {
+ Readable,
+ Writable,
+ }
+
+ // Helper structure for testing, used to define the layout of the
descriptor chain.
+ #[derive(Copy, Clone, Debug, Default)]
+ #[repr(C)]
+ struct VirtqDesc {
+ addr: Le64,
+ len: Le32,
+ flags: Le16,
+ next: Le16,
+ }
+
+ // Helper structure for testing, used to define the layout of the
available ring.
+ #[derive(Copy, Clone, Debug, Default)]
+ #[repr(C)]
+ struct VirtqAvail {
+ flags: Le16,
+ idx: Le16,
+ ring: Le16,
+ }
+
+ unsafe impl ByteValued for VirtqAvail {}
+ unsafe impl ByteValued for VirtqDesc {}
+
+ // Helper function for testing, used to create a descriptor chain with the
specified descriptors.
+ fn create_descriptor_chain(
+ memory: &GuestMemoryMmap,
+ descriptor_array_addr: GuestAddress,
+ mut buffers_start_addr: GuestAddress,
+ descriptors: Vec<(DescriptorType, u32)>,
+ ) -> DescriptorChain<&GuestMemoryMmap> {
+ let descriptors_len = descriptors.len();
+ for (index, (type_, size)) in descriptors.into_iter().enumerate() {
+ let mut flags = 0;
+ if let DescriptorType::Writable = type_ {
+ flags |= VIRTQ_DESC_F_WRITE;
+ }
+ if index + 1 < descriptors_len {
+ flags |= VIRTQ_DESC_F_NEXT;
+ }
+
+ let desc = VirtqDesc {
+ addr: buffers_start_addr.raw_value().into(),
+ len: size.into(),
+ flags: flags.into(),
+ next: (index as u16 + 1).into(),
+ };
+
+ buffers_start_addr = buffers_start_addr.checked_add(size as
u64).unwrap();
+
+ memory
+ .write_obj(
+ desc,
+ descriptor_array_addr
+ .checked_add((index *
std::mem::size_of::<VirtqDesc>()) as u64)
+ .unwrap(),
+ )
+ .unwrap();
+ }
+
+ let avail_ring = descriptor_array_addr
+ .checked_add((descriptors_len * std::mem::size_of::<VirtqDesc>())
as u64)
+ .unwrap();
+ let avail = VirtqAvail {
+ flags: 0.into(),
+ idx: 1.into(),
+ ring: 0.into(),
+ };
+ memory.write_obj(avail, avail_ring).unwrap();
+
+ let mut queue = Queue::new(4).unwrap();
+ queue
+ .try_set_desc_table_address(descriptor_array_addr)
+ .unwrap();
+ queue.try_set_avail_ring_address(avail_ring).unwrap();
+ queue.set_ready(true);
+ queue.iter(memory).unwrap().next().unwrap()
+ }
+
+ #[test]
+ fn simple_chain_reader_test() {
+ let memory_start_addr = GuestAddress(0x0);
+ let memory = GuestMemoryMmap::from_ranges(&[(memory_start_addr,
0x1000)]).unwrap();
+
+ let chain = create_descriptor_chain(
+ &memory,
+ GuestAddress(0x0),
+ GuestAddress(0x100),
+ vec![
+ (DescriptorType::Readable, 8),
+ (DescriptorType::Readable, 16),
+ (DescriptorType::Readable, 18),
+ (DescriptorType::Readable, 64),
+ ],
+ );
+
+ let mut reader = Reader::new(&memory, chain).unwrap();
+ assert_eq!(reader.available_bytes(), 106);
+ assert_eq!(reader.bytes_read(), 0);
+
+ let mut buffer = [0; 64];
+ reader.read_exact(&mut buffer).unwrap();
+ assert_eq!(reader.available_bytes(), 42);
+ assert_eq!(reader.bytes_read(), 64);
+ assert_eq!(reader.read(&mut buffer).unwrap(), 42);
+ assert_eq!(reader.available_bytes(), 0);
+ assert_eq!(reader.bytes_read(), 106);
+ }
+
+ #[test]
+ fn simple_chain_writer_test() {
+ let memory_start_addr = GuestAddress(0x0);
+ let memory = GuestMemoryMmap::from_ranges(&[(memory_start_addr,
0x1000)]).unwrap();
+
+ let chain = create_descriptor_chain(
+ &memory,
+ GuestAddress(0x0),
+ GuestAddress(0x100),
+ vec![
+ (DescriptorType::Writable, 8),
+ (DescriptorType::Writable, 16),
+ (DescriptorType::Writable, 18),
+ (DescriptorType::Writable, 64),
+ ],
+ );
+
+ let mut writer = Writer::new(&memory, chain).unwrap();
+ assert_eq!(writer.available_bytes(), 106);
+ assert_eq!(writer.bytes_written(), 0);
+
+ let buffer = [0; 64];
+ writer.write_all(&buffer).unwrap();
+ assert_eq!(writer.available_bytes(), 42);
+ assert_eq!(writer.bytes_written(), 64);
+ assert_eq!(writer.write(&buffer).unwrap(), 42);
+ assert_eq!(writer.available_bytes(), 0);
+ assert_eq!(writer.bytes_written(), 106);
+ }
+}