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 f43ab1862b feat(ovfs): support getattr and setattr (#4987)
f43ab1862b is described below
commit f43ab1862bc089977e00cca5956f13559263d6dd
Author: zjregee <[email protected]>
AuthorDate: Fri Aug 9 21:25:44 2024 +0800
feat(ovfs): support getattr and setattr (#4987)
* feat: support getattr and setattr
* typo
---
integrations/virtiofs/Cargo.toml | 10 +-
integrations/virtiofs/src/error.rs | 17 +++
integrations/virtiofs/src/filesystem.rs | 143 +++++++++++++++++++++++-
integrations/virtiofs/src/filesystem_message.rs | 48 ++++++++
4 files changed, 210 insertions(+), 8 deletions(-)
diff --git a/integrations/virtiofs/Cargo.toml b/integrations/virtiofs/Cargo.toml
index d3eca2361b..535e8e6112 100644
--- a/integrations/virtiofs/Cargo.toml
+++ b/integrations/virtiofs/Cargo.toml
@@ -32,10 +32,12 @@ anyhow = { version = "1.0.86", features = ["std"] }
libc = "0.2.139"
log = "0.4.22"
opendal = { version = "0.48.0", path = "../../core" }
-snafu = "0.8.3"
-vhost = "0.11.0"
-vhost-user-backend = "0.14.0"
-virtio-bindings = "0.2.1"
+sharded-slab = "0.1.7"
+snafu = "0.8.4"
+tokio = { version = "1.39.2", features = ["rt-multi-thread"] }
+vhost = "0.10.0"
+vhost-user-backend = "0.13.1"
+virtio-bindings = { version = "0.2.1", features = ["virtio-v5_0_0"] }
virtio-queue = "0.11.0"
vm-memory = { version = "0.14.0", features = [
"backend-mmap",
diff --git a/integrations/virtiofs/src/error.rs
b/integrations/virtiofs/src/error.rs
index d8c0ff125c..99d67a86ec 100644
--- a/integrations/virtiofs/src/error.rs
+++ b/integrations/virtiofs/src/error.rs
@@ -15,6 +15,7 @@
// specific language governing permissions and limitations
// under the License.
+use std::ffi::CStr;
use std::io;
use anyhow::Error as AnyError;
@@ -38,6 +39,22 @@ pub enum Error {
},
}
+impl From<libc::c_int> for Error {
+ fn from(errno: libc::c_int) -> Error {
+ let err_str = unsafe { libc::strerror(errno) };
+ let message = if err_str.is_null() {
+ format!("errno: {}", errno)
+ } else {
+ let c_str = unsafe { CStr::from_ptr(err_str) };
+ c_str.to_string_lossy().into_owned()
+ };
+ Error::VhostUserFsError {
+ message,
+ source: None,
+ }
+ }
+}
+
impl From<Error> for io::Error {
fn from(error: Error) -> io::Error {
match error {
diff --git a/integrations/virtiofs/src/filesystem.rs
b/integrations/virtiofs/src/filesystem.rs
index cdaedea6d0..4fb38b15dc 100644
--- a/integrations/virtiofs/src/filesystem.rs
+++ b/integrations/virtiofs/src/filesystem.rs
@@ -17,8 +17,14 @@
use std::io::Write;
use std::mem::size_of;
+use std::time::Duration;
+use log::debug;
+use opendal::ErrorKind;
use opendal::Operator;
+use sharded_slab::Slab;
+use tokio::runtime::Builder;
+use tokio::runtime::Runtime;
use vm_memory::ByteValued;
use crate::error::*;
@@ -32,21 +38,99 @@ const KERNEL_MINOR_VERSION: u32 = 38;
/// Minimum Minor version number supported.
const MIN_KERNEL_MINOR_VERSION: u32 = 27;
/// The length of the header part of the message.
-const BUFFER_HEADER_SIZE: u32 = 256;
+const BUFFER_HEADER_SIZE: u32 = 4096;
/// The maximum length of the data part of the message, used for read/write
data.
const MAX_BUFFER_SIZE: u32 = 1 << 20;
+/// The default time to live of the attributes.
+const DEFAULT_TTL: Duration = Duration::from_secs(1);
+/// The default mode of the opened file.
+const DEFAULT_OPENED_FILE_MODE: u32 = 0o755;
+
+enum FileType {
+ Dir,
+ File,
+}
+
+struct OpenedFile {
+ path: String,
+ metadata: Attr,
+}
+
+impl OpenedFile {
+ fn new(file_type: FileType, path: &str, uid: u32, gid: u32) -> OpenedFile {
+ let mut attr: Attr = unsafe { std::mem::zeroed() };
+ attr.uid = uid;
+ attr.gid = gid;
+ match file_type {
+ FileType::Dir => {
+ attr.nlink = 2;
+ attr.mode = libc::S_IFDIR | DEFAULT_OPENED_FILE_MODE;
+ }
+ FileType::File => {
+ attr.nlink = 1;
+ attr.mode = libc::S_IFREG | DEFAULT_OPENED_FILE_MODE;
+ }
+ }
+ OpenedFile {
+ path: path.to_string(),
+ metadata: attr,
+ }
+ }
+}
+
+fn opendal_error2error(error: opendal::Error) -> Error {
+ match error.kind() {
+ ErrorKind::Unsupported => Error::from(libc::EOPNOTSUPP),
+ ErrorKind::IsADirectory => Error::from(libc::EISDIR),
+ ErrorKind::NotFound => Error::from(libc::ENOENT),
+ ErrorKind::PermissionDenied => Error::from(libc::EACCES),
+ ErrorKind::AlreadyExists => Error::from(libc::EEXIST),
+ ErrorKind::NotADirectory => Error::from(libc::ENOTDIR),
+ ErrorKind::RangeNotSatisfied => Error::from(libc::EINVAL),
+ ErrorKind::RateLimited => Error::from(libc::EBUSY),
+ _ => Error::from(libc::ENOENT),
+ }
+}
+
+fn opendal_metadata2opened_file(
+ path: &str,
+ metadata: &opendal::Metadata,
+ uid: u32,
+ gid: u32,
+) -> OpenedFile {
+ let file_type = match metadata.mode() {
+ opendal::EntryMode::DIR => FileType::Dir,
+ _ => FileType::File,
+ };
+ OpenedFile::new(file_type, path, uid, gid)
+}
/// Filesystem is a filesystem implementation with opendal backend,
/// and will decode and process messages from VMs.
pub struct Filesystem {
- // FIXME: #[allow(dead_code)] here should be removed in the future.
- #[allow(dead_code)]
+ rt: Runtime,
core: Operator,
+ uid: u32,
+ gid: u32,
+ opened_files: Slab<OpenedFile>,
}
impl Filesystem {
pub fn new(core: Operator) -> Filesystem {
- Filesystem { core }
+ let rt = Builder::new_multi_thread()
+ .worker_threads(4)
+ .enable_all()
+ .build()
+ .unwrap();
+
+ // Here we set the uid and gid to 1000, which is the default value.
+ Filesystem {
+ rt,
+ core,
+ uid: 1000,
+ gid: 1000,
+ opened_files: Slab::new(),
+ }
}
pub fn handle_message(&self, mut r: Reader, w: Writer) -> Result<usize> {
@@ -60,6 +144,9 @@ impl Filesystem {
if let Ok(opcode) = Opcode::try_from(in_header.opcode) {
match opcode {
Opcode::Init => self.init(in_header, r, w),
+ Opcode::Destroy => self.destroy(in_header, r, w),
+ Opcode::Getattr => self.getattr(in_header, r, w),
+ Opcode::Setattr => self.setattr(in_header, r, w),
}
} else {
Filesystem::reply_error(in_header.unique, w)
@@ -134,4 +221,52 @@ impl Filesystem {
};
Filesystem::reply_ok(Some(out), None, in_header.unique, w)
}
+
+ fn destroy(&self, _in_header: InHeader, _r: Reader, _w: Writer) ->
Result<usize> {
+ // do nothing for destroy.
+ Ok(0)
+ }
+
+ fn getattr(&self, in_header: InHeader, _r: Reader, w: Writer) ->
Result<usize> {
+ debug!("getattr: 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 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(),
+ attr_valid_nsec: DEFAULT_TTL.subsec_nanos(),
+ attr: metadata.metadata,
+ ..Default::default()
+ };
+ Filesystem::reply_ok(Some(out), None, in_header.unique, w)
+ }
+
+ fn setattr(&self, in_header: InHeader, _r: Reader, w: Writer) ->
Result<usize> {
+ debug!("setattr: inode={}", in_header.nodeid);
+
+ // do nothing for setattr.
+ self.getattr(in_header, _r, w)
+ }
+}
+
+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);
+
+ Ok(attr)
+ }
}
diff --git a/integrations/virtiofs/src/filesystem_message.rs
b/integrations/virtiofs/src/filesystem_message.rs
index c427c5c638..dd924507e4 100644
--- a/integrations/virtiofs/src/filesystem_message.rs
+++ b/integrations/virtiofs/src/filesystem_message.rs
@@ -23,7 +23,10 @@ use crate::error::*;
/// The corresponding value needs to be aligned with the specification.
#[non_exhaustive]
pub enum Opcode {
+ Getattr = 3,
+ Setattr = 4,
Init = 26,
+ Destroy = 38,
}
impl TryFrom<u32> for Opcode {
@@ -31,12 +34,41 @@ impl TryFrom<u32> for Opcode {
fn try_from(value: u32) -> Result<Self, Self::Error> {
match value {
+ 3 => Ok(Opcode::Getattr),
+ 4 => Ok(Opcode::Setattr),
26 => Ok(Opcode::Init),
+ 38 => Ok(Opcode::Destroy),
_ => Err(new_vhost_user_fs_error("failed to decode opcode", None)),
}
}
}
+/// Attr represents the file attributes in virtiofs.
+///
+/// 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#L577
+#[repr(C)]
+#[derive(Debug, Default, Clone, Copy)]
+pub struct Attr {
+ pub ino: u64,
+ pub size: u64,
+ pub blocks: u64,
+ pub atime: u64,
+ pub mtime: u64,
+ pub ctime: u64,
+ pub atimensec: u32,
+ pub mtimensec: u32,
+ pub ctimensec: u32,
+ pub mode: u32,
+ pub nlink: u32,
+ pub uid: u32,
+ pub gid: u32,
+ pub rdev: u32,
+ pub blksize: u32,
+ pub flags: u32,
+}
+
/// InHeader represents the incoming message header in the filesystem call.
///
/// The fields of the struct need to conform to the specific format of the
virtiofs message.
@@ -105,9 +137,25 @@ pub struct InitOut {
pub unused: [u32; 7],
}
+/// 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.
+/// 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#L782
+#[repr(C)]
+#[derive(Debug, Default, Clone, Copy)]
+pub struct AttrOut {
+ pub attr_valid: u64,
+ pub attr_valid_nsec: u32,
+ pub dummy: u32,
+ pub attr: Attr,
+}
+
/// We will use ByteValued to implement the encoding and decoding
/// of these structures in shared memory.
+unsafe impl ByteValued for Attr {}
unsafe impl ByteValued for InHeader {}
unsafe impl ByteValued for OutHeader {}
unsafe impl ByteValued for InitIn {}
unsafe impl ByteValued for InitOut {}
+unsafe impl ByteValued for AttrOut {}