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 50dc066f23 feat(ovfs): add file creation and deletion (#5009)
50dc066f23 is described below
commit 50dc066f236698828a04803b3ec220cd7f3ac4af
Author: zjregee <[email protected]>
AuthorDate: Thu Aug 15 00:58:15 2024 +0800
feat(ovfs): add file creation and deletion (#5009)
* feat: add file create and delete
* make clippy happy
---
integrations/virtiofs/src/filesystem.rs | 217 +++++++++++++++++++++++-
integrations/virtiofs/src/filesystem_message.rs | 50 ++++++
2 files changed, 264 insertions(+), 3 deletions(-)
diff --git a/integrations/virtiofs/src/filesystem.rs
b/integrations/virtiofs/src/filesystem.rs
index a9fe6a8f37..5c4b1e84b8 100644
--- a/integrations/virtiofs/src/filesystem.rs
+++ b/integrations/virtiofs/src/filesystem.rs
@@ -32,7 +32,8 @@ use vm_memory::ByteValued;
use crate::error::*;
use crate::filesystem_message::*;
-use crate::virtiofs_util::{Reader, Writer};
+use crate::virtiofs_util::Reader;
+use crate::virtiofs_util::Writer;
/// Version number of this interface.
const KERNEL_VERSION: u32 = 7;
@@ -60,6 +61,15 @@ struct OpenedFile {
metadata: Attr,
}
+struct InnerWriter {
+ // #[allow(dead_code)] here will be removed after write is implemented.
+ #[allow(dead_code)]
+ writer: opendal::Writer,
+ // #[allow(dead_code)] here will be removed after write is implemented.
+ #[allow(dead_code)]
+ written: u64,
+}
+
impl OpenedFile {
fn new(file_type: FileType, path: &str, uid: u32, gid: u32) -> OpenedFile {
let mut attr: Attr = unsafe { std::mem::zeroed() };
@@ -120,6 +130,7 @@ pub struct Filesystem {
// 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>>,
+ opened_files_writer: Mutex<HashMap<String, InnerWriter>>,
}
impl Filesystem {
@@ -138,6 +149,7 @@ impl Filesystem {
gid: 1000,
opened_files: Slab::new(),
opened_files_map: Mutex::new(HashMap::new()),
+ opened_files_writer: Mutex::new(HashMap::new()),
}
}
@@ -156,6 +168,10 @@ impl Filesystem {
Opcode::Lookup => self.lookup(in_header, r, w),
Opcode::Getattr => self.getattr(in_header, r, w),
Opcode::Setattr => self.setattr(in_header, r, w),
+ Opcode::Create => self.create(in_header, r, w),
+ Opcode::Unlink => self.unlink(in_header, r, w),
+ Opcode::Open => self.open(in_header, r, w),
+ Opcode::Release => self.release(in_header, r, w),
}
} else {
Filesystem::reply_error(in_header.unique, w)
@@ -209,6 +225,33 @@ impl Filesystem {
})?;
Ok(w.bytes_written())
}
+
+ fn check_write_flags(&self, flags: u32) -> Result<(bool, bool)> {
+ let is_trunc = flags & libc::O_TRUNC as u32 != 0 || flags &
libc::O_CREAT as u32 != 0;
+ let is_append = flags & libc::O_APPEND as u32 != 0;
+
+ let mode = flags & libc::O_ACCMODE as u32;
+ let is_write = mode == libc::O_WRONLY as u32 || mode == libc::O_RDWR
as u32 || is_append;
+ if !is_write {
+ Err(Error::from(libc::EINVAL))?;
+ }
+
+ // OpenDAL only supports truncate write and append write,
+ // so O_TRUNC or O_APPEND needs to be specified explicitly
+ if (is_write && !is_trunc && !is_append) || is_trunc && !is_write {
+ Err(Error::from(libc::EINVAL))?;
+ }
+
+ let capability = self.core.info().full_capability();
+ if is_trunc && !capability.write {
+ Err(Error::from(libc::EACCES))?;
+ }
+ if is_append && !capability.write_can_append {
+ Err(Error::from(libc::EACCES))?;
+ }
+
+ Ok((is_trunc, is_append))
+ }
}
impl Filesystem {
@@ -238,7 +281,7 @@ impl Filesystem {
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];
+ let mut buf = vec![0; name_len];
r.read_exact(&mut buf).map_err(|e| {
new_unexpected_error("failed to decode protocol messages",
Some(e.into()))
})?;
@@ -272,7 +315,6 @@ impl Filesystem {
attr: metadata.metadata,
..Default::default()
};
-
Filesystem::reply_ok(Some(out), None, in_header.unique, w)
}
@@ -308,6 +350,150 @@ impl Filesystem {
// do nothing for setattr.
self.getattr(in_header, _r, w)
}
+
+ fn create(&self, in_header: InHeader, mut r: Reader, w: Writer) ->
Result<usize> {
+ let CreateIn { flags, .. } = r.read_obj().map_err(|e| {
+ new_vhost_user_fs_error("failed to decode protocol messages",
Some(e.into()))
+ })?;
+
+ let name_len = in_header.len as usize - size_of::<InHeader>() -
size_of::<CreateIn>();
+ let mut buf = vec![0; 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!("create: 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 mut attr = OpenedFile::new(FileType::File, &path, self.uid,
self.gid);
+ let inode = self
+ .opened_files
+ .insert(attr.clone())
+ .expect("failed to allocate inode");
+ attr.metadata.ino = inode as u64;
+ let mut opened_files_map = self.opened_files_map.lock().unwrap();
+ opened_files_map.insert(path.to_string(), inode as u64);
+
+ let writer = match self.rt.block_on(self.do_get_writer(&path, flags)) {
+ Ok(writer) => writer,
+ Err(_) => return Filesystem::reply_error(in_header.unique, w),
+ };
+
+ let mut opened_file_writer = self.opened_files_writer.lock().unwrap();
+ opened_file_writer.insert(path, writer);
+
+ let entry_out = EntryOut {
+ nodeid: attr.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: attr.metadata,
+ ..Default::default()
+ };
+ let open_out = OpenOut {
+ ..Default::default()
+ };
+ Filesystem::reply_ok(
+ Some(entry_out),
+ Some(open_out.as_slice()),
+ in_header.unique,
+ w,
+ )
+ }
+
+ fn unlink(&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![0; 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!("unlink: 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);
+ if self.rt.block_on(self.do_delete(&path)).is_err() {
+ return Filesystem::reply_error(in_header.unique, w);
+ }
+
+ self.opened_files.remove(in_header.nodeid as usize);
+ let mut opened_files_map = self.opened_files_map.lock().unwrap();
+ opened_files_map.remove(&path);
+
+ Filesystem::reply_ok(None::<u8>, None, in_header.unique, w)
+ }
+
+ fn open(&self, in_header: InHeader, mut r: Reader, w: Writer) ->
Result<usize> {
+ debug!("open: inode={}", in_header.nodeid);
+
+ let OpenIn { flags, .. } = r.read_obj().map_err(|e| {
+ new_vhost_user_fs_error("failed to decode protocol messages",
Some(e.into()))
+ })?;
+
+ let 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 writer = match self.rt.block_on(self.do_get_writer(&path, flags)) {
+ Ok(writer) => writer,
+ Err(_) => return Filesystem::reply_error(in_header.unique, w),
+ };
+
+ let mut opened_file_writer = self.opened_files_writer.lock().unwrap();
+ opened_file_writer.insert(path, writer);
+
+ let out = OpenOut {
+ ..Default::default()
+ };
+ Filesystem::reply_ok(Some(out), None, in_header.unique, w)
+ }
+
+ fn release(&self, in_header: InHeader, _r: Reader, w: Writer) ->
Result<usize> {
+ debug!("release: inode={}", in_header.nodeid);
+
+ let 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 mut opened_file_writer = self.opened_files_writer.lock().unwrap();
+ opened_file_writer.remove(&path);
+
+ Filesystem::reply_ok(None::<u8>, None, in_header.unique, w)
+ }
}
impl Filesystem {
@@ -329,4 +515,29 @@ impl Filesystem {
Ok(attr)
}
+
+ async fn do_get_writer(&self, path: &str, flags: u32) ->
Result<InnerWriter> {
+ let (_, is_append) = self.check_write_flags(flags)?;
+ let writer = self
+ .core
+ .writer_with(path)
+ .append(is_append)
+ .await
+ .map_err(opendal_error2error)?;
+ let written = if is_append {
+ self.core
+ .stat(path)
+ .await
+ .map_err(opendal_error2error)?
+ .content_length()
+ } else {
+ 0
+ };
+
+ Ok(InnerWriter { writer, written })
+ }
+
+ async fn do_delete(&self, path: &str) -> Result<()> {
+ self.core.delete(path).await.map_err(opendal_error2error)
+ }
}
diff --git a/integrations/virtiofs/src/filesystem_message.rs
b/integrations/virtiofs/src/filesystem_message.rs
index c26d33fbc3..1c7d9bb3f4 100644
--- a/integrations/virtiofs/src/filesystem_message.rs
+++ b/integrations/virtiofs/src/filesystem_message.rs
@@ -26,7 +26,11 @@ pub enum Opcode {
Lookup = 1,
Getattr = 3,
Setattr = 4,
+ Unlink = 10,
+ Open = 14,
+ Release = 18,
Init = 26,
+ Create = 35,
Destroy = 38,
}
@@ -38,7 +42,11 @@ impl TryFrom<u32> for Opcode {
1 => Ok(Opcode::Lookup),
3 => Ok(Opcode::Getattr),
4 => Ok(Opcode::Setattr),
+ 10 => Ok(Opcode::Unlink),
+ 14 => Ok(Opcode::Open),
+ 18 => Ok(Opcode::Release),
26 => Ok(Opcode::Init),
+ 35 => Ok(Opcode::Create),
38 => Ok(Opcode::Destroy),
_ => Err(new_vhost_user_fs_error("failed to decode opcode", None)),
}
@@ -170,6 +178,45 @@ pub struct AttrOut {
pub attr: Attr,
}
+/// CreateIn is used to parse the parameters passed in the Create 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#L881
+#[repr(C)]
+#[derive(Debug, Default, Clone, Copy)]
+pub struct CreateIn {
+ pub flags: u32,
+ pub mode: u32,
+ pub umask: u32,
+ pub open_flags: u32,
+}
+
+/// OpenIn is used to parse the parameters passed in the Open 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#873
+#[repr(C)]
+#[derive(Debug, Default, Clone, Copy)]
+pub struct OpenIn {
+ pub flags: u32,
+ pub open_flags: u32,
+}
+
+/// OpenOut is used to return the file descriptor 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#L891
+#[repr(C)]
+#[derive(Debug, Default, Clone, Copy)]
+pub struct OpenOut {
+ pub fh: u64,
+ pub open_flags: u32,
+ pub padding: u32,
+}
+
/// We will use ByteValued to implement the encoding and decoding
/// of these structures in shared memory.
unsafe impl ByteValued for Attr {}
@@ -179,3 +226,6 @@ unsafe impl ByteValued for InitIn {}
unsafe impl ByteValued for InitOut {}
unsafe impl ByteValued for EntryOut {}
unsafe impl ByteValued for AttrOut {}
+unsafe impl ByteValued for CreateIn {}
+unsafe impl ByteValued for OpenIn {}
+unsafe impl ByteValued for OpenOut {}