Skip to content

Commit

Permalink
feat(ovfs): support getattr and setattr (#4987)
Browse files Browse the repository at this point in the history
* feat: support getattr and setattr

* typo
  • Loading branch information
zjregee authored Aug 9, 2024
1 parent dac28d1 commit f43ab18
Show file tree
Hide file tree
Showing 4 changed files with 210 additions and 8 deletions.
10 changes: 6 additions & 4 deletions integrations/virtiofs/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
17 changes: 17 additions & 0 deletions integrations/virtiofs/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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 {
Expand Down
143 changes: 139 additions & 4 deletions integrations/virtiofs/src/filesystem.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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::*;
Expand All @@ -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> {
Expand All @@ -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)
Expand Down Expand Up @@ -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)
}
}
48 changes: 48 additions & 0 deletions integrations/virtiofs/src/filesystem_message.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,20 +23,52 @@ 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 {
type Error = Error;

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.
Expand Down Expand Up @@ -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 {}

0 comments on commit f43ab18

Please sign in to comment.