Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(ovfs): add file creation and deletion #5009

Merged
merged 2 commits into from
Aug 14, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
217 changes: 214 additions & 3 deletions integrations/virtiofs/src/filesystem.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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() };
Expand Down Expand Up @@ -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 {
Expand All @@ -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()),
}
}

Expand All @@ -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)
Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -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()))
})?;
Expand Down Expand Up @@ -272,7 +315,6 @@ impl Filesystem {
attr: metadata.metadata,
..Default::default()
};

Filesystem::reply_ok(Some(out), None, in_header.unique, w)
}

Expand Down Expand Up @@ -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 {
Expand All @@ -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)
}
}
50 changes: 50 additions & 0 deletions integrations/virtiofs/src/filesystem_message.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
}

Expand All @@ -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)),
}
Expand Down Expand Up @@ -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 {}
Expand All @@ -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 {}
Loading