From 5c128023da958d5ffe1bdef985916d1c5d465379 Mon Sep 17 00:00:00 2001 From: Sebastien Boeuf Date: Thu, 31 Oct 2019 13:24:10 -0700 Subject: [PATCH] vhost_user_fs: Add Server structure to consume FileSystem implementation Add a Server type that links the FUSE protocol with the virtio transport. It parses messages sent on the virtio queue and then calls the appropriate method of the Filesystem trait. This code has been ported over from crosvm commit 961461350c0b6824e5f20655031bf6c6bf6b7c30. One small modification has been applied to the original code. Because cloud-hypervisor didn't have the macro used by crosvm, the match statement in the function handle_message() has been updated. Signed-off-by: Sebastien Boeuf --- vhost_user_fs/src/lib.rs | 44 ++ vhost_user_fs/src/server.rs | 1269 +++++++++++++++++++++++++++++++++++ 2 files changed, 1313 insertions(+) create mode 100644 vhost_user_fs/src/server.rs diff --git a/vhost_user_fs/src/lib.rs b/vhost_user_fs/src/lib.rs index 1f11ff546..048396c7c 100644 --- a/vhost_user_fs/src/lib.rs +++ b/vhost_user_fs/src/lib.rs @@ -11,3 +11,47 @@ pub mod filesystem; pub mod fuse; pub mod multikey; pub mod passthrough; +pub mod server; + +use std::ffi::FromBytesWithNulError; +use std::{error, fmt, io}; + +#[derive(Debug)] +pub enum Error { + /// Failed to decode protocol messages. + DecodeMessage(io::Error), + /// Failed to encode protocol messages. + EncodeMessage(io::Error), + /// One or more parameters are missing. + MissingParameter, + /// A C string parameter is invalid. + InvalidCString(FromBytesWithNulError), + /// The `len` field of the header is too small. + InvalidHeaderLength, + /// The `size` field of the `SetxattrIn` message does not match the length + /// of the decoded value. + InvalidXattrSize((u32, usize)), +} + +impl error::Error for Error {} + +impl fmt::Display for Error { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + use Error::*; + match self { + DecodeMessage(err) => write!(f, "failed to decode fuse message: {}", err), + EncodeMessage(err) => write!(f, "failed to encode fuse message: {}", err), + MissingParameter => write!(f, "one or more parameters are missing"), + InvalidHeaderLength => write!(f, "the `len` field of the header is too small"), + InvalidCString(err) => write!(f, "a c string parameter is invalid: {}", err), + InvalidXattrSize((size, len)) => write!( + f, + "The `size` field of the `SetxattrIn` message does not match the length of the\ + decoded value: size = {}, value.len() = {}", + size, len + ), + } + } +} + +pub type Result = ::std::result::Result; diff --git a/vhost_user_fs/src/server.rs b/vhost_user_fs/src/server.rs new file mode 100644 index 000000000..b429aa5cd --- /dev/null +++ b/vhost_user_fs/src/server.rs @@ -0,0 +1,1269 @@ +// Copyright 2019 The Chromium OS Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +use std::ffi::CStr; +use std::fs::File; +use std::io::{self, Read, Write}; +use std::mem::size_of; + +use libc; +use vm_memory::ByteValued; + +use crate::descriptor_utils::{Reader, Writer}; +use crate::filesystem::{ + Context, DirEntry, Entry, FileSystem, GetxattrReply, ListxattrReply, ZeroCopyReader, + ZeroCopyWriter, +}; +use crate::fuse::*; +use crate::{Error, Result}; + +const MAX_BUFFER_SIZE: u32 = (1 << 20); +const DIRENT_PADDING: [u8; 8] = [0; 8]; + +struct ZCReader<'a>(Reader<'a>); + +impl<'a> ZeroCopyReader for ZCReader<'a> { + fn read_to(&mut self, f: &mut File, count: usize, off: u64) -> io::Result { + self.0.read_to_at(f, count, off) + } +} + +impl<'a> io::Read for ZCReader<'a> { + fn read(&mut self, buf: &mut [u8]) -> io::Result { + self.0.read(buf) + } +} + +struct ZCWriter<'a>(Writer<'a>); + +impl<'a> ZeroCopyWriter for ZCWriter<'a> { + fn write_from(&mut self, f: &mut File, count: usize, off: u64) -> io::Result { + self.0.write_from_at(f, count, off) + } +} + +impl<'a> io::Write for ZCWriter<'a> { + fn write(&mut self, buf: &[u8]) -> io::Result { + self.0.write(buf) + } + + fn flush(&mut self) -> io::Result<()> { + self.0.flush() + } +} + +pub struct Server { + fs: F, +} + +impl Server { + pub fn new(fs: F) -> Server { + Server { fs } + } + + #[allow(clippy::cognitive_complexity)] + pub fn handle_message(&self, mut r: Reader, w: Writer) -> Result { + let in_header: InHeader = r.read_obj().map_err(Error::DecodeMessage)?; + + if in_header.len > MAX_BUFFER_SIZE { + return reply_error( + io::Error::from_raw_os_error(libc::ENOMEM), + in_header.unique, + w, + ); + } + match in_header.opcode { + x if x == Opcode::Lookup as u32 => self.lookup(in_header, r, w), + x if x == Opcode::Forget as u32 => self.forget(in_header, r), // No reply. + x if x == Opcode::Getattr as u32 => self.getattr(in_header, r, w), + x if x == Opcode::Setattr as u32 => self.setattr(in_header, r, w), + x if x == Opcode::Readlink as u32 => self.readlink(in_header, w), + x if x == Opcode::Symlink as u32 => self.symlink(in_header, r, w), + x if x == Opcode::Mknod as u32 => self.mknod(in_header, r, w), + x if x == Opcode::Mkdir as u32 => self.mkdir(in_header, r, w), + x if x == Opcode::Unlink as u32 => self.unlink(in_header, r, w), + x if x == Opcode::Rmdir as u32 => self.rmdir(in_header, r, w), + x if x == Opcode::Rename as u32 => self.rename(in_header, r, w), + x if x == Opcode::Link as u32 => self.link(in_header, r, w), + x if x == Opcode::Open as u32 => self.open(in_header, r, w), + x if x == Opcode::Read as u32 => self.read(in_header, r, w), + x if x == Opcode::Write as u32 => self.write(in_header, r, w), + x if x == Opcode::Statfs as u32 => self.statfs(in_header, w), + x if x == Opcode::Release as u32 => self.release(in_header, r, w), + x if x == Opcode::Fsync as u32 => self.fsync(in_header, r, w), + x if x == Opcode::Setxattr as u32 => self.setxattr(in_header, r, w), + x if x == Opcode::Getxattr as u32 => self.getxattr(in_header, r, w), + x if x == Opcode::Listxattr as u32 => self.listxattr(in_header, r, w), + x if x == Opcode::Removexattr as u32 => self.removexattr(in_header, r, w), + x if x == Opcode::Flush as u32 => self.flush(in_header, r, w), + x if x == Opcode::Init as u32 => self.init(in_header, r, w), + x if x == Opcode::Opendir as u32 => self.opendir(in_header, r, w), + x if x == Opcode::Readdir as u32 => self.readdir(in_header, r, w), + x if x == Opcode::Releasedir as u32 => self.releasedir(in_header, r, w), + x if x == Opcode::Fsyncdir as u32 => self.fsyncdir(in_header, r, w), + x if x == Opcode::Getlk as u32 => self.getlk(in_header, r, w), + x if x == Opcode::Setlk as u32 => self.setlk(in_header, r, w), + x if x == Opcode::Setlkw as u32 => self.setlkw(in_header, r, w), + x if x == Opcode::Access as u32 => self.access(in_header, r, w), + x if x == Opcode::Create as u32 => self.create(in_header, r, w), + x if x == Opcode::Interrupt as u32 => self.interrupt(in_header), + x if x == Opcode::Bmap as u32 => self.bmap(in_header, r, w), + x if x == Opcode::Destroy as u32 => self.destroy(), + x if x == Opcode::Ioctl as u32 => self.ioctl(in_header, r, w), + x if x == Opcode::Poll as u32 => self.poll(in_header, r, w), + x if x == Opcode::NotifyReply as u32 => self.notify_reply(in_header, r, w), + x if x == Opcode::BatchForget as u32 => self.batch_forget(in_header, r, w), + x if x == Opcode::Fallocate as u32 => self.fallocate(in_header, r, w), + x if x == Opcode::Readdirplus as u32 => self.readdirplus(in_header, r, w), + x if x == Opcode::Rename2 as u32 => self.rename2(in_header, r, w), + x if x == Opcode::Lseek as u32 => self.lseek(in_header, r, w), + _ => reply_error( + io::Error::from_raw_os_error(libc::ENOSYS), + in_header.unique, + w, + ), + } + } + + fn lookup(&self, in_header: InHeader, mut r: Reader, w: Writer) -> Result { + let namelen = (in_header.len as usize) + .checked_sub(size_of::()) + .ok_or(Error::InvalidHeaderLength)?; + + let mut buf = vec![0u8; namelen]; + + r.read_exact(&mut buf).map_err(Error::DecodeMessage)?; + + let name = bytes_to_cstr(buf.as_ref())?; + + match self + .fs + .lookup(Context::from(in_header), in_header.nodeid.into(), &name) + { + Ok(entry) => { + let out = EntryOut::from(entry); + + reply_ok(Some(out), None, in_header.unique, w) + } + Err(e) => reply_error(e, in_header.unique, w), + } + } + + fn forget(&self, in_header: InHeader, mut r: Reader) -> Result { + let ForgetIn { nlookup } = r.read_obj().map_err(Error::DecodeMessage)?; + + self.fs + .forget(Context::from(in_header), in_header.nodeid.into(), nlookup); + + // There is no reply for forget messages. + Ok(0) + } + + fn getattr(&self, in_header: InHeader, mut r: Reader, w: Writer) -> Result { + let GetattrIn { flags, fh, .. } = r.read_obj().map_err(Error::DecodeMessage)?; + + let handle = if (flags & GETATTR_FH) != 0 { + Some(fh.into()) + } else { + None + }; + + match self + .fs + .getattr(Context::from(in_header), in_header.nodeid.into(), handle) + { + Ok((st, timeout)) => { + let out = AttrOut { + attr_valid: timeout.as_secs(), + attr_valid_nsec: timeout.subsec_nanos(), + dummy: 0, + attr: st.into(), + }; + reply_ok(Some(out), None, in_header.unique, w) + } + Err(e) => reply_error(e, in_header.unique, w), + } + } + + fn setattr(&self, in_header: InHeader, mut r: Reader, w: Writer) -> Result { + let setattr_in: SetattrIn = r.read_obj().map_err(Error::DecodeMessage)?; + + let handle = if setattr_in.valid & FATTR_FH != 0 { + Some(setattr_in.fh.into()) + } else { + None + }; + + let valid = SetattrValid::from_bits_truncate(setattr_in.valid); + + let st: libc::stat64 = setattr_in.into(); + + match self.fs.setattr( + Context::from(in_header), + in_header.nodeid.into(), + st, + handle, + valid, + ) { + Ok((st, timeout)) => { + let out = AttrOut { + attr_valid: timeout.as_secs(), + attr_valid_nsec: timeout.subsec_nanos(), + dummy: 0, + attr: st.into(), + }; + reply_ok(Some(out), None, in_header.unique, w) + } + Err(e) => reply_error(e, in_header.unique, w), + } + } + + fn readlink(&self, in_header: InHeader, w: Writer) -> Result { + match self + .fs + .readlink(Context::from(in_header), in_header.nodeid.into()) + { + Ok(linkname) => { + // We need to disambiguate the option type here even though it is `None`. + reply_ok(None::, Some(&linkname), in_header.unique, w) + } + Err(e) => reply_error(e, in_header.unique, w), + } + } + + fn symlink(&self, in_header: InHeader, mut r: Reader, w: Writer) -> Result { + // Unfortunately the name and linkname are encoded one after another and + // separated by a nul character. + let len = (in_header.len as usize) + .checked_sub(size_of::()) + .ok_or(Error::InvalidHeaderLength)?; + let mut buf = vec![0; len]; + + r.read_exact(&mut buf).map_err(Error::DecodeMessage)?; + + // We want to include the '\0' byte in the first slice. + let split_pos = buf + .iter() + .position(|c| *c == b'\0') + .map(|p| p + 1) + .ok_or(Error::MissingParameter)?; + + let (name, linkname) = buf.split_at(split_pos); + + match self.fs.symlink( + Context::from(in_header), + bytes_to_cstr(linkname)?, + in_header.nodeid.into(), + bytes_to_cstr(name)?, + ) { + Ok(entry) => { + let out = EntryOut::from(entry); + + reply_ok(Some(out), None, in_header.unique, w) + } + Err(e) => reply_error(e, in_header.unique, w), + } + } + + fn mknod(&self, in_header: InHeader, mut r: Reader, w: Writer) -> Result { + let MknodIn { + mode, rdev, umask, .. + } = r.read_obj().map_err(Error::DecodeMessage)?; + + let namelen = (in_header.len as usize) + .checked_sub(size_of::()) + .and_then(|l| l.checked_sub(size_of::())) + .ok_or(Error::InvalidHeaderLength)?; + let mut name = vec![0; namelen]; + + r.read_exact(&mut name).map_err(Error::DecodeMessage)?; + + match self.fs.mknod( + Context::from(in_header), + in_header.nodeid.into(), + bytes_to_cstr(&name)?, + mode, + rdev, + umask, + ) { + Ok(entry) => { + let out = EntryOut::from(entry); + + reply_ok(Some(out), None, in_header.unique, w) + } + Err(e) => reply_error(e, in_header.unique, w), + } + } + + fn mkdir(&self, in_header: InHeader, mut r: Reader, w: Writer) -> Result { + let MkdirIn { mode, umask } = r.read_obj().map_err(Error::DecodeMessage)?; + + let namelen = (in_header.len as usize) + .checked_sub(size_of::()) + .and_then(|l| l.checked_sub(size_of::())) + .ok_or(Error::InvalidHeaderLength)?; + let mut name = vec![0; namelen]; + + r.read_exact(&mut name).map_err(Error::DecodeMessage)?; + + match self.fs.mkdir( + Context::from(in_header), + in_header.nodeid.into(), + bytes_to_cstr(&name)?, + mode, + umask, + ) { + Ok(entry) => { + let out = EntryOut::from(entry); + + reply_ok(Some(out), None, in_header.unique, w) + } + Err(e) => reply_error(e, in_header.unique, w), + } + } + + fn unlink(&self, in_header: InHeader, mut r: Reader, w: Writer) -> Result { + let namelen = (in_header.len as usize) + .checked_sub(size_of::()) + .ok_or(Error::InvalidHeaderLength)?; + let mut name = vec![0; namelen]; + + r.read_exact(&mut name).map_err(Error::DecodeMessage)?; + + match self.fs.unlink( + Context::from(in_header), + in_header.nodeid.into(), + bytes_to_cstr(&name)?, + ) { + Ok(()) => reply_ok(None::, None, in_header.unique, w), + Err(e) => reply_error(e, in_header.unique, w), + } + } + + fn rmdir(&self, in_header: InHeader, mut r: Reader, w: Writer) -> Result { + let namelen = (in_header.len as usize) + .checked_sub(size_of::()) + .ok_or(Error::InvalidHeaderLength)?; + let mut name = vec![0; namelen]; + + r.read_exact(&mut name).map_err(Error::DecodeMessage)?; + + match self.fs.rmdir( + Context::from(in_header), + in_header.nodeid.into(), + bytes_to_cstr(&name)?, + ) { + Ok(()) => reply_ok(None::, None, in_header.unique, w), + Err(e) => reply_error(e, in_header.unique, w), + } + } + + fn do_rename( + &self, + in_header: InHeader, + msg_size: usize, + newdir: u64, + flags: u32, + mut r: Reader, + w: Writer, + ) -> Result { + let buflen = (in_header.len as usize) + .checked_sub(size_of::()) + .and_then(|l| l.checked_sub(msg_size)) + .ok_or(Error::InvalidHeaderLength)?; + let mut buf = vec![0; buflen]; + + r.read_exact(&mut buf).map_err(Error::DecodeMessage)?; + + // We want to include the '\0' byte in the first slice. + let split_pos = buf + .iter() + .position(|c| *c == b'\0') + .map(|p| p + 1) + .ok_or(Error::MissingParameter)?; + + let (oldname, newname) = buf.split_at(split_pos); + + match self.fs.rename( + Context::from(in_header), + in_header.nodeid.into(), + bytes_to_cstr(oldname)?, + newdir.into(), + bytes_to_cstr(newname)?, + flags, + ) { + Ok(()) => reply_ok(None::, None, in_header.unique, w), + Err(e) => reply_error(e, in_header.unique, w), + } + } + + fn rename(&self, in_header: InHeader, mut r: Reader, w: Writer) -> Result { + let RenameIn { newdir } = r.read_obj().map_err(Error::DecodeMessage)?; + + self.do_rename(in_header, size_of::(), newdir, 0, r, w) + } + + fn rename2(&self, in_header: InHeader, mut r: Reader, w: Writer) -> Result { + let Rename2In { newdir, flags, .. } = r.read_obj().map_err(Error::DecodeMessage)?; + + let flags = flags & (libc::RENAME_EXCHANGE | libc::RENAME_NOREPLACE) as u32; + + self.do_rename(in_header, size_of::(), newdir, flags, r, w) + } + + fn link(&self, in_header: InHeader, mut r: Reader, w: Writer) -> Result { + let LinkIn { oldnodeid } = r.read_obj().map_err(Error::DecodeMessage)?; + + let namelen = (in_header.len as usize) + .checked_sub(size_of::()) + .and_then(|l| l.checked_sub(size_of::())) + .ok_or(Error::InvalidHeaderLength)?; + let mut name = vec![0; namelen]; + + r.read_exact(&mut name).map_err(Error::DecodeMessage)?; + + match self.fs.link( + Context::from(in_header), + oldnodeid.into(), + in_header.nodeid.into(), + bytes_to_cstr(&name)?, + ) { + Ok(entry) => { + let out = EntryOut::from(entry); + + reply_ok(Some(out), None, in_header.unique, w) + } + Err(e) => reply_error(e, in_header.unique, w), + } + } + + fn open(&self, in_header: InHeader, mut r: Reader, w: Writer) -> Result { + let OpenIn { flags, .. } = r.read_obj().map_err(Error::DecodeMessage)?; + + match self + .fs + .open(Context::from(in_header), in_header.nodeid.into(), flags) + { + Ok((handle, opts)) => { + let out = OpenOut { + fh: handle.map(Into::into).unwrap_or(0), + open_flags: opts.bits(), + ..Default::default() + }; + + reply_ok(Some(out), None, in_header.unique, w) + } + Err(e) => reply_error(e, in_header.unique, w), + } + } + + fn read(&self, in_header: InHeader, mut r: Reader, mut w: Writer) -> Result { + let ReadIn { + fh, + offset, + size, + read_flags, + lock_owner, + flags, + .. + } = r.read_obj().map_err(Error::DecodeMessage)?; + + if size > MAX_BUFFER_SIZE { + return reply_error( + io::Error::from_raw_os_error(libc::ENOMEM), + in_header.unique, + w, + ); + } + + let owner = if read_flags & READ_LOCKOWNER != 0 { + Some(lock_owner) + } else { + None + }; + + // Split the writer into 2 pieces: one for the `OutHeader` and the rest for the data. + let data_writer = ZCWriter(w.split_at(size_of::()).unwrap()); + + match self.fs.read( + Context::from(in_header), + in_header.nodeid.into(), + fh.into(), + data_writer, + size, + offset, + owner, + flags, + ) { + Ok(count) => { + // Don't use `reply_ok` because we need to set a custom size length for the + // header. + let out = OutHeader { + len: (size_of::() + count) as u32, + error: 0, + unique: in_header.unique, + }; + + w.write_all(out.as_slice()).map_err(Error::EncodeMessage)?; + Ok(out.len as usize) + } + Err(e) => reply_error(e, in_header.unique, w), + } + } + + fn write(&self, in_header: InHeader, mut r: Reader, w: Writer) -> Result { + let WriteIn { + fh, + offset, + size, + write_flags, + lock_owner, + flags, + .. + } = r.read_obj().map_err(Error::DecodeMessage)?; + + if size > MAX_BUFFER_SIZE { + return reply_error( + io::Error::from_raw_os_error(libc::ENOMEM), + in_header.unique, + w, + ); + } + + let owner = if write_flags & WRITE_LOCKOWNER != 0 { + Some(lock_owner) + } else { + None + }; + + let delayed_write = write_flags & WRITE_CACHE != 0; + + let data_reader = ZCReader(r); + + match self.fs.write( + Context::from(in_header), + in_header.nodeid.into(), + fh.into(), + data_reader, + size, + offset, + owner, + delayed_write, + flags, + ) { + Ok(count) => { + let out = WriteOut { + size: count as u32, + ..Default::default() + }; + + reply_ok(Some(out), None, in_header.unique, w) + } + Err(e) => reply_error(e, in_header.unique, w), + } + } + + fn statfs(&self, in_header: InHeader, w: Writer) -> Result { + match self + .fs + .statfs(Context::from(in_header), in_header.nodeid.into()) + { + Ok(st) => reply_ok(Some(Kstatfs::from(st)), None, in_header.unique, w), + Err(e) => reply_error(e, in_header.unique, w), + } + } + + fn release(&self, in_header: InHeader, mut r: Reader, w: Writer) -> Result { + let ReleaseIn { + fh, + flags, + release_flags, + lock_owner, + } = r.read_obj().map_err(Error::DecodeMessage)?; + + let flush = release_flags & RELEASE_FLUSH != 0; + let flock_release = release_flags & RELEASE_FLOCK_UNLOCK != 0; + let lock_owner = if flush || flock_release { + Some(lock_owner) + } else { + None + }; + + match self.fs.release( + Context::from(in_header), + in_header.nodeid.into(), + flags, + fh.into(), + flush, + flock_release, + lock_owner, + ) { + Ok(()) => reply_ok(None::, None, in_header.unique, w), + Err(e) => reply_error(e, in_header.unique, w), + } + } + + fn fsync(&self, in_header: InHeader, mut r: Reader, w: Writer) -> Result { + let FsyncIn { + fh, fsync_flags, .. + } = r.read_obj().map_err(Error::DecodeMessage)?; + let datasync = fsync_flags & 0x1 != 0; + + match self.fs.fsync( + Context::from(in_header), + in_header.nodeid.into(), + datasync, + fh.into(), + ) { + Ok(()) => reply_ok(None::, None, in_header.unique, w), + Err(e) => reply_error(e, in_header.unique, w), + } + } + + fn setxattr(&self, in_header: InHeader, mut r: Reader, w: Writer) -> Result { + let SetxattrIn { size, flags } = r.read_obj().map_err(Error::DecodeMessage)?; + + // The name and value and encoded one after another and separated by a '\0' character. + let len = (in_header.len as usize) + .checked_sub(size_of::()) + .and_then(|l| l.checked_sub(size_of::())) + .ok_or(Error::InvalidHeaderLength)?; + let mut buf = vec![0; len]; + + r.read_exact(&mut buf).map_err(Error::DecodeMessage)?; + + // We want to include the '\0' byte in the first slice. + let split_pos = buf + .iter() + .position(|c| *c == b'\0') + .map(|p| p + 1) + .ok_or(Error::MissingParameter)?; + + let (name, value) = buf.split_at(split_pos); + + if size != value.len() as u32 { + return Err(Error::InvalidXattrSize((size, value.len()))); + } + + match self.fs.setxattr( + Context::from(in_header), + in_header.nodeid.into(), + bytes_to_cstr(name)?, + value, + flags, + ) { + Ok(()) => reply_ok(None::, None, in_header.unique, w), + Err(e) => reply_error(e, in_header.unique, w), + } + } + + fn getxattr(&self, in_header: InHeader, mut r: Reader, w: Writer) -> Result { + let GetxattrIn { size, .. } = r.read_obj().map_err(Error::DecodeMessage)?; + + let namelen = (in_header.len as usize) + .checked_sub(size_of::()) + .and_then(|l| l.checked_sub(size_of::())) + .ok_or(Error::InvalidHeaderLength)?; + let mut name = vec![0; namelen]; + + r.read_exact(&mut name).map_err(Error::DecodeMessage)?; + + if size > MAX_BUFFER_SIZE { + return reply_error( + io::Error::from_raw_os_error(libc::ENOMEM), + in_header.unique, + w, + ); + } + + match self.fs.getxattr( + Context::from(in_header), + in_header.nodeid.into(), + bytes_to_cstr(&name)?, + size, + ) { + Ok(GetxattrReply::Value(val)) => reply_ok(None::, Some(&val), in_header.unique, w), + Ok(GetxattrReply::Count(count)) => { + let out = GetxattrOut { + size: count, + ..Default::default() + }; + + reply_ok(Some(out), None, in_header.unique, w) + } + Err(e) => reply_error(e, in_header.unique, w), + } + } + + fn listxattr(&self, in_header: InHeader, mut r: Reader, w: Writer) -> Result { + let GetxattrIn { size, .. } = r.read_obj().map_err(Error::DecodeMessage)?; + + if size > MAX_BUFFER_SIZE { + return reply_error( + io::Error::from_raw_os_error(libc::ENOMEM), + in_header.unique, + w, + ); + } + + match self + .fs + .listxattr(Context::from(in_header), in_header.nodeid.into(), size) + { + Ok(ListxattrReply::Names(val)) => reply_ok(None::, Some(&val), in_header.unique, w), + Ok(ListxattrReply::Count(count)) => { + let out = GetxattrOut { + size: count, + ..Default::default() + }; + + reply_ok(Some(out), None, in_header.unique, w) + } + Err(e) => reply_error(e, in_header.unique, w), + } + } + + fn removexattr(&self, in_header: InHeader, mut r: Reader, w: Writer) -> Result { + let namelen = (in_header.len as usize) + .checked_sub(size_of::()) + .ok_or(Error::InvalidHeaderLength)?; + + let mut buf = vec![0; namelen]; + + r.read_exact(&mut buf).map_err(Error::DecodeMessage)?; + + let name = bytes_to_cstr(&buf)?; + + match self + .fs + .removexattr(Context::from(in_header), in_header.nodeid.into(), name) + { + Ok(()) => reply_ok(None::, None, in_header.unique, w), + Err(e) => reply_error(e, in_header.unique, w), + } + } + + fn flush(&self, in_header: InHeader, mut r: Reader, w: Writer) -> Result { + let FlushIn { fh, lock_owner, .. } = r.read_obj().map_err(Error::DecodeMessage)?; + + match self.fs.flush( + Context::from(in_header), + in_header.nodeid.into(), + fh.into(), + lock_owner, + ) { + Ok(()) => reply_ok(None::, None, in_header.unique, w), + Err(e) => reply_error(e, in_header.unique, w), + } + } + + fn init(&self, in_header: InHeader, mut r: Reader, w: Writer) -> Result { + let InitIn { + major, + minor, + max_readahead, + flags, + } = r.read_obj().map_err(Error::DecodeMessage)?; + + if major < KERNEL_VERSION { + error!("Unsupported fuse protocol version: {}.{}", major, minor); + return reply_error( + io::Error::from_raw_os_error(libc::EPROTO), + in_header.unique, + w, + ); + } + + if major > KERNEL_VERSION { + // Wait for the kernel to reply back with a 7.X version. + let out = InitOut { + major: KERNEL_VERSION, + minor: KERNEL_MINOR_VERSION, + ..Default::default() + }; + + return reply_ok(Some(out), None, in_header.unique, w); + } + + if minor < KERNEL_MINOR_VERSION { + error!( + "Unsupported fuse protocol minor version: {}.{}", + major, minor + ); + return reply_error( + io::Error::from_raw_os_error(libc::EPROTO), + in_header.unique, + w, + ); + } + + // These fuse features are supported by this server by default. + let supported = FsOptions::ASYNC_READ + | FsOptions::PARALLEL_DIROPS + | FsOptions::BIG_WRITES + | FsOptions::AUTO_INVAL_DATA + | FsOptions::HANDLE_KILLPRIV + | FsOptions::ASYNC_DIO + | FsOptions::HAS_IOCTL_DIR + | FsOptions::ATOMIC_O_TRUNC; + + let capable = FsOptions::from_bits_truncate(flags); + + match self.fs.init(capable) { + Ok(want) => { + let enabled = capable & (want | supported); + + let out = InitOut { + major: KERNEL_VERSION, + minor: KERNEL_MINOR_VERSION, + max_readahead, + flags: enabled.bits(), + max_background: ::std::u16::MAX, + congestion_threshold: (::std::u16::MAX / 4) * 3, + max_write: MAX_BUFFER_SIZE, + time_gran: 1, // nanoseconds + ..Default::default() + }; + + reply_ok(Some(out), None, in_header.unique, w) + } + Err(e) => reply_error(e, in_header.unique, w), + } + } + + fn opendir(&self, in_header: InHeader, mut r: Reader, w: Writer) -> Result { + let OpenIn { flags, .. } = r.read_obj().map_err(Error::DecodeMessage)?; + + match self + .fs + .opendir(Context::from(in_header), in_header.nodeid.into(), flags) + { + Ok((handle, opts)) => { + let out = OpenOut { + fh: handle.map(Into::into).unwrap_or(0), + open_flags: opts.bits(), + ..Default::default() + }; + + reply_ok(Some(out), None, in_header.unique, w) + } + Err(e) => reply_error(e, in_header.unique, w), + } + } + + fn do_readdir( + &self, + in_header: InHeader, + mut r: Reader, + mut w: Writer, + plus: bool, + ) -> Result { + let ReadIn { + fh, offset, size, .. + } = r.read_obj().map_err(Error::DecodeMessage)?; + + if size > MAX_BUFFER_SIZE { + return reply_error( + io::Error::from_raw_os_error(libc::ENOMEM), + in_header.unique, + w, + ); + } + + let available_bytes = w.available_bytes(); + if available_bytes < size as usize { + return reply_error( + io::Error::from_raw_os_error(libc::ENOMEM), + in_header.unique, + w, + ); + } + + // Skip over enough bytes for the header. + let mut cursor = w.split_at(size_of::()).unwrap(); + + let res = if plus { + self.fs.readdirplus( + Context::from(in_header), + in_header.nodeid.into(), + fh.into(), + size, + offset, + |d, e| add_dirent(&mut cursor, size, d, Some(e)), + ) + } else { + self.fs.readdir( + Context::from(in_header), + in_header.nodeid.into(), + fh.into(), + size, + offset, + |d| add_dirent(&mut cursor, size, d, None), + ) + }; + + if let Err(e) = res { + reply_error(e, in_header.unique, w) + } else { + // Don't use `reply_ok` because we need to set a custom size length for the + // header. + let out = OutHeader { + len: (size_of::() + cursor.bytes_written()) as u32, + error: 0, + unique: in_header.unique, + }; + + w.write_all(out.as_slice()).map_err(Error::EncodeMessage)?; + Ok(out.len as usize) + } + } + + fn readdir(&self, in_header: InHeader, r: Reader, w: Writer) -> Result { + self.do_readdir(in_header, r, w, false) + } + + fn readdirplus(&self, in_header: InHeader, r: Reader, w: Writer) -> Result { + self.do_readdir(in_header, r, w, true) + } + + fn releasedir(&self, in_header: InHeader, mut r: Reader, w: Writer) -> Result { + let ReleaseIn { fh, flags, .. } = r.read_obj().map_err(Error::DecodeMessage)?; + + match self.fs.releasedir( + Context::from(in_header), + in_header.nodeid.into(), + flags, + fh.into(), + ) { + Ok(()) => reply_ok(None::, None, in_header.unique, w), + Err(e) => reply_error(e, in_header.unique, w), + } + } + + fn fsyncdir(&self, in_header: InHeader, mut r: Reader, w: Writer) -> Result { + let FsyncIn { + fh, fsync_flags, .. + } = r.read_obj().map_err(Error::DecodeMessage)?; + let datasync = fsync_flags & 0x1 != 0; + + match self.fs.fsyncdir( + Context::from(in_header), + in_header.nodeid.into(), + datasync, + fh.into(), + ) { + Ok(()) => reply_ok(None::, None, in_header.unique, w), + Err(e) => reply_error(e, in_header.unique, w), + } + } + + fn getlk(&self, in_header: InHeader, mut _r: Reader, w: Writer) -> Result { + if let Err(e) = self.fs.getlk() { + reply_error(e, in_header.unique, w) + } else { + Ok(0) + } + } + + fn setlk(&self, in_header: InHeader, mut _r: Reader, w: Writer) -> Result { + if let Err(e) = self.fs.setlk() { + reply_error(e, in_header.unique, w) + } else { + Ok(0) + } + } + + fn setlkw(&self, in_header: InHeader, mut _r: Reader, w: Writer) -> Result { + if let Err(e) = self.fs.setlkw() { + reply_error(e, in_header.unique, w) + } else { + Ok(0) + } + } + + fn access(&self, in_header: InHeader, mut r: Reader, w: Writer) -> Result { + let AccessIn { mask, .. } = r.read_obj().map_err(Error::DecodeMessage)?; + + match self + .fs + .access(Context::from(in_header), in_header.nodeid.into(), mask) + { + Ok(()) => reply_ok(None::, None, in_header.unique, w), + Err(e) => reply_error(e, in_header.unique, w), + } + } + + fn create(&self, in_header: InHeader, mut r: Reader, w: Writer) -> Result { + let CreateIn { + flags, mode, umask, .. + } = r.read_obj().map_err(Error::DecodeMessage)?; + + let namelen = (in_header.len as usize) + .checked_sub(size_of::()) + .and_then(|l| l.checked_sub(size_of::())) + .ok_or(Error::InvalidHeaderLength)?; + + let mut buf = vec![0; namelen]; + + r.read_exact(&mut buf).map_err(Error::DecodeMessage)?; + + let name = bytes_to_cstr(&buf)?; + + match self.fs.create( + Context::from(in_header), + in_header.nodeid.into(), + name, + mode, + flags, + umask, + ) { + Ok((entry, handle, opts)) => { + let entry_out = EntryOut { + nodeid: entry.inode, + generation: entry.generation, + entry_valid: entry.entry_timeout.as_secs(), + attr_valid: entry.attr_timeout.as_secs(), + entry_valid_nsec: entry.entry_timeout.subsec_nanos(), + attr_valid_nsec: entry.attr_timeout.subsec_nanos(), + attr: entry.attr.into(), + }; + let open_out = OpenOut { + fh: handle.map(Into::into).unwrap_or(0), + open_flags: opts.bits(), + ..Default::default() + }; + + // Kind of a hack to write both structs. + reply_ok( + Some(entry_out), + Some(open_out.as_slice()), + in_header.unique, + w, + ) + } + Err(e) => reply_error(e, in_header.unique, w), + } + } + + fn interrupt(&self, _in_header: InHeader) -> Result { + Ok(0) + } + + fn bmap(&self, in_header: InHeader, mut _r: Reader, w: Writer) -> Result { + if let Err(e) = self.fs.bmap() { + reply_error(e, in_header.unique, w) + } else { + Ok(0) + } + } + + fn destroy(&self) -> Result { + // No reply to this function. + self.fs.destroy(); + + Ok(0) + } + + fn ioctl(&self, in_header: InHeader, _r: Reader, w: Writer) -> Result { + if let Err(e) = self.fs.ioctl() { + reply_error(e, in_header.unique, w) + } else { + Ok(0) + } + } + + fn poll(&self, in_header: InHeader, mut _r: Reader, w: Writer) -> Result { + if let Err(e) = self.fs.poll() { + reply_error(e, in_header.unique, w) + } else { + Ok(0) + } + } + + fn notify_reply(&self, in_header: InHeader, mut _r: Reader, w: Writer) -> Result { + if let Err(e) = self.fs.notify_reply() { + reply_error(e, in_header.unique, w) + } else { + Ok(0) + } + } + + fn batch_forget(&self, in_header: InHeader, mut r: Reader, w: Writer) -> Result { + let BatchForgetIn { count, .. } = r.read_obj().map_err(Error::DecodeMessage)?; + + if let Some(size) = (count as usize).checked_mul(size_of::()) { + if size > MAX_BUFFER_SIZE as usize { + return reply_error( + io::Error::from_raw_os_error(libc::ENOMEM), + in_header.unique, + w, + ); + } + } else { + return reply_error( + io::Error::from_raw_os_error(libc::EOVERFLOW), + in_header.unique, + w, + ); + } + + let mut requests = Vec::with_capacity(count as usize); + for _ in 0..count { + requests.push( + r.read_obj::() + .map(|f| (f.nodeid.into(), f.nlookup)) + .map_err(Error::DecodeMessage)?, + ); + } + + self.fs.batch_forget(Context::from(in_header), requests); + + // No reply for forget messages. + Ok(0) + } + + fn fallocate(&self, in_header: InHeader, mut r: Reader, w: Writer) -> Result { + let FallocateIn { + fh, + offset, + length, + mode, + .. + } = r.read_obj().map_err(Error::DecodeMessage)?; + + match self.fs.fallocate( + Context::from(in_header), + in_header.nodeid.into(), + fh.into(), + mode, + offset, + length, + ) { + Ok(()) => reply_ok(None::, None, in_header.unique, w), + Err(e) => reply_error(e, in_header.unique, w), + } + } + + fn lseek(&self, in_header: InHeader, mut _r: Reader, w: Writer) -> Result { + if let Err(e) = self.fs.lseek() { + reply_error(e, in_header.unique, w) + } else { + Ok(0) + } + } +} + +fn reply_ok( + out: Option, + data: Option<&[u8]>, + unique: u64, + mut w: Writer, +) -> Result { + let mut len = size_of::(); + + if out.is_some() { + len += size_of::(); + } + + if let Some(ref data) = data { + len += data.len(); + } + + let header = OutHeader { + len: len as u32, + error: 0, + unique, + }; + + w.write_all(header.as_slice()) + .map_err(Error::EncodeMessage)?; + + if let Some(out) = out { + w.write_all(out.as_slice()).map_err(Error::EncodeMessage)?; + } + + if let Some(data) = data { + w.write_all(data).map_err(Error::EncodeMessage)?; + } + + debug_assert_eq!(len, w.bytes_written()); + Ok(w.bytes_written()) +} + +fn reply_error(e: io::Error, unique: u64, mut w: Writer) -> Result { + let header = OutHeader { + len: size_of::() as u32, + error: -e.raw_os_error().unwrap_or(libc::EIO), + unique, + }; + + w.write_all(header.as_slice()) + .map_err(Error::EncodeMessage)?; + + debug_assert_eq!(header.len as usize, w.bytes_written()); + Ok(w.bytes_written()) +} + +fn bytes_to_cstr(buf: &[u8]) -> Result<&CStr> { + // Convert to a `CStr` first so that we can drop the '\0' byte at the end + // and make sure there are no interior '\0' bytes. + CStr::from_bytes_with_nul(buf).map_err(Error::InvalidCString) +} + +fn add_dirent( + cursor: &mut Writer, + max: u32, + d: DirEntry, + entry: Option, +) -> io::Result { + if d.name.len() > ::std::u32::MAX as usize { + return Err(io::Error::from_raw_os_error(libc::EOVERFLOW)); + } + + let dirent_len = size_of::() + .checked_add(d.name.len()) + .ok_or_else(|| io::Error::from_raw_os_error(libc::EOVERFLOW))?; + + // Directory entries must be padded to 8-byte alignment. If adding 7 causes + // an overflow then this dirent cannot be properly padded. + let padded_dirent_len = dirent_len + .checked_add(7) + .map(|l| l & !7) + .ok_or_else(|| io::Error::from_raw_os_error(libc::EOVERFLOW))?; + + let total_len = if entry.is_some() { + padded_dirent_len + .checked_add(size_of::()) + .ok_or_else(|| io::Error::from_raw_os_error(libc::EOVERFLOW))? + } else { + padded_dirent_len + }; + + if (max as usize).saturating_sub(cursor.bytes_written()) < total_len { + Ok(0) + } else { + if let Some(entry) = entry { + cursor.write_all(EntryOut::from(entry).as_slice())?; + } + + let dirent = Dirent { + ino: d.ino, + off: d.offset, + namelen: d.name.len() as u32, + type_: d.type_, + }; + + cursor.write_all(dirent.as_slice())?; + cursor.write_all(d.name)?; + + // We know that `dirent_len` <= `padded_dirent_len` due to the check above + // so there's no need for checked arithmetic. + let padding = padded_dirent_len - dirent_len; + if padding > 0 { + cursor.write_all(&DIRENT_PADDING[..padding])?; + } + + Ok(total_len) + } +}