--- /dev/null
+[source]
+[source.debian-packages]
+directory = "/usr/share/cargo/registry"
+[source.crates-io]
+replace-with = "debian-packages"
--- /dev/null
+/target
+Cargo.lock
+test
--- /dev/null
+[package]
+name = "proxmox-fuse"
+version = "0.1.0"
+authors = ["Wolfgang Bumiller <w.bumiller@proxmox.com>"]
+edition = "2018"
+license = "AGPL-3"
+description = "Expose fuse requests as async streams."
+
+exclude = [ "debian" ]
+
+[dependencies]
+anyhow = "1.0"
+futures = "0.3"
+libc = "0.2"
+mio = "0.6.21"
+tokio = { version = "0.2", features = ["io-driver", "macros", "signal", "stream"] }
--- /dev/null
+//! Implements "block" based sparse vector.
+
+use std::io;
+
+pub struct Extent {
+ offset: u64,
+ data: Vec<u8>,
+}
+
+enum SearchBias {
+ Low,
+ High,
+}
+
+pub struct BlockFile {
+ extents: Vec<Extent>,
+ block_size: usize,
+ block_mask: u64,
+ max_extent_size: usize,
+}
+
+impl BlockFile {
+ /// Panics if `block_size` is not a power of two.
+ pub fn new(block_size: usize) -> Self {
+ if !block_size.is_power_of_two() {
+ panic!("block size must be a power of two");
+ }
+
+ let block_mask = ((block_size as u64) << 1) - 1;
+ let max_extent_size = 0x7FFF_FFFF & !(block_mask as usize);
+
+ Self {
+ extents: Vec::new(),
+ block_size,
+ block_mask,
+ max_extent_size,
+ }
+ }
+
+ pub fn size(&self) -> u64 {
+ self.extents
+ .last()
+ .map(|e| e.offset + e.data.len() as u64)
+ .unwrap_or(0)
+ }
+
+ /// A binary search which allows choosing whether the lower or higher key should be returned when
+ /// there's no exact match.
+ ///
+ /// Returns -1 if `offset` is smaller than the first extent (eg when first writing to offset
+ /// 4096 and then to 0).
+ /// Returns `self.size()` if `offset` is past the currently written data.
+ /// Returns an index otherwise.
+ fn search_extent(&self, offset: u64, bias: SearchBias) -> Result<usize, isize> {
+ let mut a = -1isize;
+ let mut b = self.extents.len() as isize;
+
+ while (b - a) > 1 {
+ let i = a + (b - a) / 2; // since `(a + b)/2` might overflow... in theory
+ let entry_ofs = self.extents[i as usize].offset;
+ if offset < entry_ofs {
+ b = i;
+ } else if offset > entry_ofs {
+ a = i;
+ } else {
+ return Ok(i as usize);
+ }
+ }
+
+ Err(match bias {
+ SearchBias::Low => a,
+ SearchBias::High => b,
+ })
+ }
+
+ fn get_read_extents(&self, offset: u64) -> &[Extent] {
+ match self.search_extent(offset, SearchBias::Low) {
+ Ok(index) => &self.extents[index..],
+ Err(beg) => {
+ assert!(beg >= -1);
+ let beg = beg.max(0) as usize;
+ assert!((beg as u64) <= self.size());
+ &self.extents[beg..]
+ }
+ }
+ }
+
+ pub fn read(&self, mut buf: &mut [u8], mut offset: u64) -> io::Result<usize> {
+ let end = offset + buf.len() as u64;
+ if end > self.size() {
+ let remaining = self.size() - offset;
+ buf = &mut buf[..(remaining as usize)]
+ }
+ let return_size = buf.len();
+
+ for extent in self.get_read_extents(offset) {
+ let data = if extent.offset <= offset {
+ // in the first one we may be starting in the middle:
+ let inside = (offset - extent.offset) as usize;
+ &extent.data[inside..]
+ } else {
+ let empty_len = extent.offset - offset;
+ if empty_len > (buf.len() as u64) {
+ break;
+ }
+ let (to_clear, remaining) = buf.split_at_mut(empty_len as usize);
+ unsafe {
+ std::ptr::write_bytes(to_clear.as_mut_ptr(), 0, to_clear.len());
+ }
+ offset += to_clear.len() as u64;
+ buf = remaining;
+ &extent.data[..]
+ };
+
+ let data_len = data.len().min(buf.len());
+ let (to_write, remaining) = buf.split_at_mut(data_len);
+ to_write.copy_from_slice(&data[..data_len]);
+ offset += to_write.len() as u64;
+ buf = remaining;
+ }
+
+ // clear the remaining buffer with zeroes:
+ unsafe {
+ std::ptr::write_bytes(buf.as_mut_ptr(), 0, buf.len());
+ }
+
+ Ok(return_size)
+ }
+
+ pub fn truncate(&mut self, size: u64) {
+ match self.search_extent(size, SearchBias::Low) {
+ Ok(index) => self.extents.truncate(index),
+ Err(-1) => self.extents.truncate(0),
+ Err(index) => {
+ let index = index as usize;
+ self.extents.truncate(index + 1);
+ let begin = self.extents[index].offset;
+ self.extents[index].data.truncate((size - begin) as usize);
+ }
+ }
+ }
+
+ pub fn write(&mut self, buf: &[u8], offset: u64) -> io::Result<()> {
+ let block_mask_usize = self.block_mask as usize;
+ if (buf.len() + block_mask_usize) & !block_mask_usize > self.max_extent_size {
+ let mut offset = offset;
+ for chunk in buf.chunks(self.max_extent_size) {
+ self.write(chunk, offset)?;
+ offset += chunk.len() as u64;
+ }
+ return Ok(());
+ }
+
+ let index = match self.search_extent(offset, SearchBias::Low) {
+ Err(-1) => {
+ // This is the very first extent to be written.
+ let block_ofs = offset & !self.block_mask;
+ let data_end_ofs = offset + buf.len() as u64;
+ let extent_size = (data_end_ofs - block_ofs) as usize;
+ let mut data = Vec::with_capacity(extent_size);
+ let leading_zeros = (offset - block_ofs) as usize;
+ unsafe {
+ data.set_len(extent_size);
+ let to_zero = &mut data[..leading_zeros];
+ std::ptr::write_bytes(to_zero.as_mut_ptr(), 0, to_zero.len());
+ }
+ data[leading_zeros..].copy_from_slice(buf);
+ self.extents.push(Extent {
+ offset: block_ofs,
+ data,
+ });
+ return Ok(());
+ }
+ Ok(index) => index,
+ Err(index) => {
+ assert!(index >= 0);
+ index as usize
+ }
+ };
+
+ // We write in part to the extent at `index` and may want to merge with the extents
+ // following it.
+
+ let (extent, further_extents) = self.extents[index..].split_first_mut().unwrap();
+
+ let block_mask_usize = self.block_mask as usize;
+ let in_ofs = (offset - extent.offset) as usize;
+ let in_end = in_ofs + buf.len();
+ let in_block_end = (in_end + block_mask_usize) & !block_mask_usize;
+ if in_block_end > self.max_extent_size {
+ let possible = self.max_extent_size - in_ofs;
+ self.write(&buf[..possible], offset)?;
+ return self.write(&buf[possible..], offset + (possible as u64));
+ }
+
+ // at this point we know we will not exceed the maximum extent size:
+
+ if extent.data.len() >= in_end {
+ // we're not resizing the extent, so just wite and leave
+ extent.data[in_ofs..in_end].copy_from_slice(buf);
+ return Ok(());
+ }
+
+ // we definitely need to resize:
+ let mut needed_end = in_end;
+ if !further_extents.is_empty() {
+ needed_end = (needed_end + block_mask_usize) & !block_mask_usize;
+ }
+
+ extent.data.reserve(needed_end - extent.data.len());
+ unsafe {
+ extent.data.set_len(needed_end);
+ let to_zero = &mut extent.data[in_end..];
+ std::ptr::write_bytes(to_zero.as_mut_ptr(), 0, to_zero.len());
+ }
+
+ extent.data[in_ofs..in_end].copy_from_slice(buf);
+
+ let cur_end = extent.offset + extent.data.len() as u64;
+
+ // data has been written, now handle the trailing extents:
+ let next = match further_extents.first_mut() {
+ None => return Ok(()),
+ Some(next) => next,
+ };
+
+ if cur_end <= extent.offset {
+ return Ok(());
+ }
+
+ let over_offset = extent.offset + (in_end as u64);
+ let over_by = (over_offset - next.offset) as usize;
+ let to_move_end = (over_by + block_mask_usize) & !block_mask_usize;
+ let to_move_size = to_move_end - over_by;
+ let extent_data_len = extent.data.len();
+ extent.data[(extent_data_len - to_move_size)..].copy_from_slice(&next.data[..to_move_size]);
+ next.offset += to_move_end as u64;
+ next.data = next.data.split_off(to_move_end);
+ Ok(())
+ }
+}
--- /dev/null
+//! The tmpfs.
+
+use std::collections::BTreeMap;
+use std::convert::TryFrom;
+use std::ffi::{OsStr, OsString};
+use std::sync::atomic::{AtomicUsize, Ordering};
+use std::sync::{Mutex, RwLock};
+use std::time::Duration;
+use std::{io, mem};
+
+use anyhow::Error;
+
+use crate::block_file::BlockFile;
+
+fn now() -> Duration {
+ std::time::SystemTime::now()
+ .duration_since(std::time::UNIX_EPOCH)
+ .unwrap()
+}
+
+fn new_stat(inode: u64, mode: libc::mode_t, uid: libc::uid_t, gid: libc::gid_t) -> libc::stat {
+ let mut stat: libc::stat = unsafe { mem::zeroed() };
+ stat.st_ino = inode;
+ stat.st_mode = mode;
+ stat.st_uid = uid;
+ stat.st_gid = gid;
+ let now = now();
+ stat.st_mtime = now.as_secs() as i64;
+ stat.st_mtime_nsec = i64::from(now.subsec_nanos());
+ stat.st_atime = stat.st_mtime;
+ stat.st_atime_nsec = stat.st_mtime_nsec;
+ stat.st_ctime = stat.st_mtime;
+ stat.st_ctime_nsec = stat.st_mtime_nsec;
+ stat
+}
+
+fn new_dir_stat(inode: u64) -> libc::stat {
+ new_stat(inode, 0o755 | libc::S_IFDIR, 0, 0)
+}
+
+pub struct Fs {
+ entries: RwLock<Vec<Option<Box<FsEntry>>>>,
+ free_inodes: Mutex<Vec<u64>>,
+}
+
+impl Fs {
+ pub fn new() -> Self {
+ Self {
+ entries: RwLock::new(vec![
+ None,
+ Some(Box::new(FsEntry {
+ inode: proxmox_fuse::ROOT_ID,
+ parent: proxmox_fuse::ROOT_ID,
+ lookups: AtomicUsize::new(1),
+ links: AtomicUsize::new(1),
+ stat: RwLock::new(new_dir_stat(1)),
+ content: FsContent::Dir(Dir::new()),
+ })),
+ ]),
+ free_inodes: Mutex::new(Vec::new()),
+ }
+ }
+
+ pub fn lookup(&self, inode: u64) -> io::Result<InodeLookup> {
+ let inode = usize::try_from(inode).map_err(|_| {
+ // we could have never created such an inode for the kernel...
+ io_format_err!("kernel accessed unexpected too-large inode: {}", inode)
+ })?;
+ match self.entries.read().unwrap().get(inode) {
+ Some(Some(entry)) => {
+ let entry: &FsEntry = entry;
+ let entry = entry as *const FsEntry;
+ Ok(InodeLookup::new(self, unsafe { &*entry }))
+ }
+ // This inode has been deleted...
+ Some(None) => io_return!(libc::ENOENT),
+ // This inode has never been advertised to the kernel:
+ None => io_bail!("kernel looked up never-advertised inode: {}", inode),
+ }
+ }
+
+ pub fn lookup_at(&self, inode: u64, name: &OsStr) -> io::Result<InodeLookup> {
+ let node = self.lookup(inode)?;
+
+ if name == OsStr::new(".") {
+ return Ok(node);
+ }
+
+ match &node.content {
+ FsContent::Dir(dir) => {
+ if name == OsStr::new("..") {
+ return self.lookup(node.parent);
+ }
+
+ match dir.files.read().unwrap().get(name) {
+ Some(inode) => self.lookup(*inode),
+ None => io_return!(libc::ENOENT),
+ }
+ }
+ _ => io_return!(libc::ENOTDIR),
+ }
+ }
+
+ unsafe fn forget_entry(&self, entry: &FsEntry, nlookup: usize) {
+ if entry.lookups.fetch_sub(nlookup, Ordering::AcqRel) > 1 {
+ // there were still more lookups present
+ return;
+ }
+
+ // lookup count dropped to zero, check the hard links:
+ if entry.links.load(Ordering::Acquire) != 0 {
+ return;
+ }
+
+ let inode = entry.inode;
+
+ entry.on_drop(self);
+ drop(entry);
+
+ // no lookups, no hard links, delete:
+ eprintln!("Deleting inode {}", inode);
+ let mut entries_lock = self.entries.write().unwrap();
+ entries_lock[inode as usize] = None;
+ drop(entries_lock);
+ self.free_inodes.lock().unwrap().push(inode);
+ }
+
+ pub fn forget(&self, inode: u64, nlookup: usize) -> io::Result<()> {
+ let entries_lock = self.entries.read().unwrap();
+ match entries_lock.get(inode as usize).as_ref() {
+ Some(Some(entry)) => {
+ unsafe {
+ let entry = entry.as_ref() as *const FsEntry;
+ drop(entries_lock);
+ self.forget_entry(&*entry, nlookup);
+ }
+ Ok(())
+ }
+ Some(None) => io_return!(libc::ENOENT),
+ None => io_bail!("tried to forget a never-looked-up inode"),
+ }
+ }
+
+ fn do_create(
+ &self,
+ parent: u64,
+ name: OsString,
+ mode: libc::mode_t,
+ fs_content: FsContent,
+ ) -> io::Result<InodeLookup> {
+ use std::collections::btree_map::Entry::*;
+ let parent_dir = self.lookup(parent)?;
+
+ match &parent_dir.content {
+ FsContent::Dir(content) => {
+ let mut content = content.files.write().unwrap();
+ match content.entry(name) {
+ Occupied(_) => io_return!(libc::EEXIST),
+ Vacant(vacancy) => {
+ let mut stat = new_stat(0, mode, 0, 0);
+
+ // create an inode and put a write-lock the entry list
+ let inode = self.free_inodes.lock().unwrap().pop();
+ let mut entry_lock = self.entries.write().unwrap();
+ let inode = match inode {
+ Some(inode) => inode,
+ None => {
+ let inode = entry_lock.len();
+ entry_lock.push(None);
+ inode as u64
+ }
+ };
+
+ // create the directory
+ stat.st_ino = inode;
+ let dir = Box::new(FsEntry {
+ inode,
+ parent,
+ lookups: AtomicUsize::new(0),
+ links: AtomicUsize::new(1),
+ stat: RwLock::new(stat),
+ content: fs_content,
+ });
+
+ let ptr = &*dir as *const FsEntry;
+
+ // Insert into the file system:
+ entry_lock[inode as usize] = Some(dir);
+ // Hardlink the inode into the directory
+ vacancy.insert(inode);
+
+ Ok(InodeLookup::new(self, unsafe { &*ptr }))
+ }
+ }
+ }
+ _ => io_return!(libc::ENOTDIR),
+ }
+ }
+
+ pub fn mkdir(
+ &self,
+ parent: u64,
+ name: OsString,
+ mode: libc::mode_t,
+ ) -> io::Result<InodeLookup> {
+ self.do_create(
+ parent,
+ name,
+ mode | libc::S_IFDIR,
+ FsContent::Dir(Dir::new()),
+ )
+ }
+
+ pub fn create(
+ &self,
+ parent: u64,
+ name: OsString,
+ mode: libc::mode_t,
+ ) -> io::Result<InodeLookup> {
+ self.do_create(
+ parent,
+ name,
+ mode | libc::S_IFREG,
+ FsContent::File(File::new()),
+ )
+ }
+
+ pub fn unlink(&self, parent: u64, name: &OsStr, is_rmdir: bool) -> Result<(), Error> {
+ let parent_dir = self.lookup(parent)?;
+ let entry = match &parent_dir.content {
+ FsContent::Dir(content) => {
+ let mut content = content.files.write().unwrap();
+
+ // FIXME: once BTreeMap::remove_entry is stable, use this to avoid cloning `name`.
+ let inode = match content.remove(name) {
+ Some(entry) => entry,
+ None => io_return!(libc::ENOENT),
+ };
+
+ let entry = self.lookup(inode)?;
+ match &entry.content {
+ FsContent::Dir(_) if !is_rmdir => {
+ content.insert(name.to_owned(), inode);
+ io_return!(libc::EISDIR);
+ }
+ FsContent::Dir(dir) if !dir.is_empty() => {
+ content.insert(name.to_owned(), inode);
+ io_return!(libc::ENOTEMPTY);
+ }
+ FsContent::Dir(_) => (),
+ _ if is_rmdir => {
+ content.insert(name.to_owned(), inode);
+ io_return!(libc::ENOTDIR);
+ }
+ _ => (),
+ }
+
+ entry
+ }
+ _ => io_return!(libc::ENOTDIR),
+ };
+
+ entry.links.fetch_sub(1, Ordering::AcqRel);
+
+ Ok(())
+ }
+
+ pub fn write(&self, inode: u64, data: &[u8], offset: u64) -> io::Result<()> {
+ let node = self.lookup(inode)?;
+ match &node.content {
+ FsContent::File(file) => {
+ let new_size = file.write(data, offset)?;
+ node.stat.write().unwrap().st_size = new_size as libc::off_t;
+ Ok(())
+ }
+ _ => io_return!(libc::EBADF),
+ }
+ }
+
+ pub fn read(&self, inode: u64, data: &mut [u8], offset: u64) -> io::Result<usize> {
+ let node = self.lookup(inode)?;
+ match &node.content {
+ FsContent::File(file) => file.read(data, offset),
+ _ => io_return!(libc::EBADF),
+ }
+ }
+}
+
+pub struct InodeLookup<'a> {
+ fs: &'a Fs,
+ entry: Option<&'a FsEntry>,
+}
+
+impl<'a> Drop for InodeLookup<'a> {
+ fn drop(&mut self) {
+ if let Some(entry) = self.entry.take() {
+ unsafe {
+ self.fs.forget_entry(entry, 1);
+ }
+ }
+ }
+}
+
+impl<'a> InodeLookup<'a> {
+ fn new(fs: &'a Fs, entry: &'a FsEntry) -> InodeLookup<'a> {
+ entry.lookups.fetch_add(1, Ordering::AcqRel);
+ Self {
+ fs,
+ entry: Some(entry),
+ }
+ }
+
+ pub fn leak(mut self) -> &'a FsEntry {
+ self.entry.take().unwrap()
+ }
+
+ pub fn increment_lookup(&self) {
+ self.entry.unwrap().lookups.fetch_add(1, Ordering::AcqRel);
+ }
+}
+
+impl<'a> std::ops::Deref for InodeLookup<'a> {
+ type Target = FsEntry;
+
+ fn deref(&self) -> &Self::Target {
+ self.entry.clone().unwrap()
+ }
+}
+
+pub struct FsEntry {
+ pub inode: u64,
+ pub parent: u64,
+ pub lookups: AtomicUsize,
+ pub links: AtomicUsize,
+ pub stat: RwLock<libc::stat>,
+ pub content: FsContent,
+}
+
+impl FsEntry {
+ fn try_add_link(&self) -> io::Result<()> {
+ loop {
+ let links = self.links.load(Ordering::Acquire);
+ if links == 0 {
+ eprintln!("Tried to increase a hardlink count of 0");
+ io_return!(libc::ENOENT);
+ }
+ if self
+ .links
+ .compare_exchange(links, links + 1, Ordering::SeqCst, Ordering::SeqCst)
+ .is_ok()
+ {
+ return Ok(());
+ }
+ }
+ }
+
+ fn on_drop(&self, fs: &Fs) {
+ if let FsContent::Dir(dir) = &self.content {
+ if let Err(err) = dir.on_drop(fs) {
+ eprintln!("error cleaning out directory: {}", err);
+ }
+ }
+ }
+}
+
+pub enum FsContent {
+ Dir(Dir),
+ File(File),
+}
+
+pub struct Dir {
+ pub files: RwLock<BTreeMap<OsString, u64>>,
+}
+
+impl Dir {
+ fn new() -> Self {
+ Self {
+ files: RwLock::new(BTreeMap::new()),
+ }
+ }
+
+ fn is_empty(&self) -> bool {
+ self.files.read().unwrap().is_empty()
+ }
+
+ fn on_drop(&self, fs: &Fs) -> io::Result<()> {
+ let files = mem::take(&mut *self.files.write().unwrap());
+
+ for inode in files.values() {
+ fs.lookup(*inode)?.links.fetch_sub(1, Ordering::AcqRel);
+ }
+
+ Ok(())
+ }
+}
+
+pub struct File {
+ data: RwLock<BlockFile>,
+}
+
+impl File {
+ fn new() -> Self {
+ Self {
+ data: RwLock::new(BlockFile::new(4096)),
+ }
+ }
+
+ /// Returns the new absolute file size, so we can update the stat member.
+ fn write(&self, data: &[u8], offset: u64) -> io::Result<u64> {
+ let mut content = self.data.write().unwrap();
+ content.write(data, offset)?;
+ Ok(content.size())
+ }
+
+ /// Returns the amount of bytes actually read.
+ fn read(&self, data: &mut [u8], offset: u64) -> io::Result<usize> {
+ self.data.read().unwrap().read(data, offset)
+ }
+}
--- /dev/null
+macro_rules! io_format_err {
+ ($($fmt:tt)*) => {
+ ::std::io::Error::new(::std::io::ErrorKind::Other, format!($($fmt)*))
+ }
+}
+
+macro_rules! io_bail {
+ ($($fmt:tt)*) => { return Err(io_format_err!($($fmt)*).into()); }
+}
+
+macro_rules! io_return {
+ ($errno:expr) => {
+ return Err(::std::io::Error::from_raw_os_error($errno).into());
+ };
+}
--- /dev/null
+use std::convert::TryFrom;
+use std::ffi::OsStr;
+use std::path::Path;
+use std::{io, mem};
+
+use anyhow::{bail, format_err, Error};
+use futures::future::FutureExt;
+use futures::select;
+use futures::stream::TryStreamExt;
+use tokio::signal::unix::{signal, SignalKind};
+
+use proxmox_fuse::requests::{self, FuseRequest, SetTime};
+use proxmox_fuse::{EntryParam, Fuse, ReplyBufState, Request};
+
+#[macro_use]
+pub mod macros;
+
+pub mod block_file;
+pub mod fs;
+
+use fs::Fs;
+
+#[tokio::main]
+async fn main() -> Result<(), Error> {
+ let mut args = std::env::args_os().skip(1);
+
+ let path = args.next().ok_or_else(|| format_err!("missing path"))?;
+
+ let mut interrupt = signal(SignalKind::interrupt())?;
+ let fuse = Fuse::builder("mytmpfs")?
+ .debug()
+ .enable_readdir()
+ .enable_mkdir()
+ .enable_rmdir()
+ .enable_create()
+ .enable_unlink()
+ .enable_mknod()
+ .enable_setattr()
+ .enable_read()
+ .enable_write()
+ .build()?
+ .mount(Path::new(&path))?;
+
+ select! {
+ res = handle_fuse(fuse).fuse() => res?,
+ _ = interrupt.recv().fuse() => {
+ eprintln!("interrupted");
+ }
+ }
+
+ Ok(())
+}
+
+fn to_entry_param(stat: &libc::stat) -> EntryParam {
+ EntryParam {
+ inode: stat.st_ino,
+ generation: 1,
+ attr: stat.clone(),
+ attr_timeout: std::f64::MAX,
+ entry_timeout: std::f64::MAX,
+ }
+}
+
+fn handle_io_err(
+ err: io::Error,
+ reply: impl FnOnce(io::Error) -> io::Result<()>,
+) -> Result<(), Error> {
+ // `io_bail` is used for error reporting where we return `EIO`
+ if err.kind() == io::ErrorKind::Other {
+ eprintln!("An IO error occured: {}", err);
+ }
+ reply(err)?;
+ Ok(())
+}
+
+fn handle_err(err: Error, reply: impl FnOnce(io::Error) -> io::Result<()>) -> Result<(), Error> {
+ match err.downcast::<io::Error>() {
+ Ok(err) => handle_io_err(err, reply),
+ Err(err) => {
+ // `bail` (non-`io::Error`) is used for fatal errors which should actually cancel:
+ eprintln!("internal error: {}", err);
+ Err(err)
+ }
+ }
+}
+
+async fn handle_fuse(mut fuse: Fuse) -> Result<(), Error> {
+ let fs = Fs::new();
+
+ while let Some(request) = fuse.try_next().await? {
+ match request {
+ Request::Getattr(request) => match fs.lookup(request.inode) {
+ Ok(node) => request.reply(&node.leak().stat.read().unwrap(), std::f64::MAX)?,
+ Err(err) => handle_io_err(err, |err| request.io_fail(err))?,
+ },
+ Request::Setattr(mut request) => match handle_setattr(&fs, &mut request) {
+ Ok(node) => request.reply(&node.stat.read().unwrap(), std::f64::MAX)?,
+ Err(err) => handle_err(err, |err| request.io_fail(err))?,
+ },
+ Request::Lookup(request) => match fs.lookup_at(request.parent, &request.file_name) {
+ Ok(node) => request.reply(&to_entry_param(&node.leak().stat.read().unwrap()))?,
+ Err(err) => handle_io_err(err, |err| request.io_fail(err))?,
+ },
+ Request::Forget(request) => match fs.forget(request.inode, request.count as usize) {
+ Ok(()) => request.reply(),
+ Err(err) => eprintln!("error forgetting inode {}: {}", request.inode, err),
+ },
+ Request::Readdir(mut request) => match handle_readdir(&fs, &mut request) {
+ Ok(()) => request.reply()?,
+ Err(err) => handle_err(err, |err| request.io_fail(err))?,
+ },
+ Request::Mkdir(mut request) => {
+ let reply = fs.mkdir(
+ request.parent,
+ mem::take(&mut request.dir_name),
+ request.mode,
+ );
+ match reply {
+ Ok(entry) => {
+ request.reply(&to_entry_param(&entry.leak().stat.read().unwrap()))?
+ }
+ Err(err) => handle_io_err(err, |err| request.io_fail(err))?,
+ }
+ }
+ Request::Create(mut request) => {
+ let reply = fs.create(
+ request.parent,
+ mem::take(&mut request.file_name),
+ request.mode,
+ );
+ match reply {
+ Ok(entry) => {
+ // CREATE acts as `Lookup` + `Open`
+ entry.increment_lookup();
+ request.reply(&to_entry_param(&entry.leak().stat.read().unwrap()), 0)?
+ }
+ Err(err) => handle_io_err(err, |err| request.io_fail(err))?,
+ }
+ }
+ Request::Mknod(mut request) => {
+ let reply = fs.create(
+ request.parent,
+ mem::take(&mut request.file_name),
+ request.mode,
+ );
+ match reply {
+ Ok(entry) => {
+ request.reply(&to_entry_param(&entry.leak().stat.read().unwrap()))?
+ }
+ Err(err) => handle_io_err(err, |err| request.io_fail(err))?,
+ }
+ }
+ Request::Open(request) => match fs.lookup(request.inode) {
+ Ok(node) => request.reply(&to_entry_param(&node.leak().stat.read().unwrap()), 0)?,
+ Err(err) => handle_io_err(err, |err| request.io_fail(err))?,
+ },
+ Request::Release(request) => match fs.forget(request.inode, 1) {
+ Ok(()) => request.reply()?,
+ Err(err) => handle_io_err(err, |err| request.io_fail(err))?,
+ },
+ Request::Unlink(request) => {
+ match fs.unlink(request.parent, &request.file_name, false) {
+ Ok(()) => request.reply()?,
+ Err(err) => handle_err(err, |err| request.io_fail(err))?,
+ }
+ }
+ Request::Rmdir(request) => match fs.unlink(request.parent, &request.dir_name, true) {
+ Ok(()) => request.reply()?,
+ Err(err) => handle_err(err, |err| request.io_fail(err))?,
+ },
+ Request::Write(request) => {
+ match fs.write(request.inode, request.data(), request.offset) {
+ Ok(()) => {
+ let len = request.data().len();
+ request.reply(len)?;
+ }
+ Err(err) => handle_io_err(err, |err| request.io_fail(err))?,
+ }
+ }
+ Request::Read(request) => {
+ // For simplicity we just limit reads to 1 MiB for now...
+ let size = request.size.min(1024 * 1024);
+ let mut buf = Vec::with_capacity(size);
+ unsafe {
+ buf.set_len(size);
+ }
+ match fs.read(request.inode, &mut buf, request.offset) {
+ Ok(got) => {
+ unsafe {
+ buf.set_len(got);
+ }
+ request.reply(&buf)?;
+ }
+ Err(err) => handle_io_err(err, |err| request.io_fail(err))?,
+ }
+ }
+ other => bail!("Got unknown request: {:?}", other),
+ }
+ }
+ Ok(())
+}
+
+fn handle_readdir(fs: &Fs, request: &mut requests::Readdir) -> Result<(), Error> {
+ let offset = match isize::try_from(request.offset) {
+ Ok(offset) => offset,
+ Err(_) => bail!("bad offset"),
+ };
+
+ let dir = fs.lookup(request.inode)?;
+
+ match &dir.content {
+ fs::FsContent::Dir(content) => {
+ let files = content.files.read().unwrap();
+ let file_count = files.len() as isize;
+ let mut next = offset;
+ for (name, &inode) in files.iter().skip(offset as usize) {
+ next += 1;
+ let inode = fs.lookup(inode)?;
+ let stat = inode.stat.read().unwrap();
+ match request.add_entry(&name, &stat, next)? {
+ ReplyBufState::Ok => (),
+ ReplyBufState::Full => return Ok(()),
+ }
+ }
+ drop(files);
+
+ if next == file_count {
+ next += 1;
+ let inode = fs.lookup(dir.parent)?;
+ let stat = inode.stat.read().unwrap();
+ match request.add_entry(OsStr::new(".."), &stat, next)? {
+ ReplyBufState::Ok => (),
+ ReplyBufState::Full => return Ok(()),
+ }
+ }
+
+ if next == file_count + 1 {
+ next += 1;
+ match request.add_entry(OsStr::new("."), &dir.stat.read().unwrap(), next)? {
+ ReplyBufState::Ok => (),
+ ReplyBufState::Full => return Ok(()),
+ }
+ }
+
+ Ok(())
+ }
+ _ => io_return!(libc::ENOTDIR),
+ }
+}
+
+fn handle_setattr<'a>(
+ fs: &'a Fs,
+ request: &mut requests::Setattr,
+) -> Result<fs::InodeLookup<'a>, Error> {
+ use std::time::SystemTime;
+
+ let file = fs.lookup(request.inode)?;
+
+ let mut stat = file.stat.write().unwrap();
+ let mut now = None;
+
+ if let Some(mode) = request.mode() {
+ stat.st_mode = (stat.st_mode & libc::S_IFMT) | mode
+ }
+
+ if let Some(uid) = request.uid() {
+ stat.st_uid = uid;
+ }
+
+ if let Some(gid) = request.gid() {
+ stat.st_gid = gid;
+ }
+
+ if let Some(size) = request.size() {
+ stat.st_size = size as libc::off_t;
+ }
+
+ if let Some(time) = match request.atime() {
+ Some(SetTime::Now) => {
+ let new_now = SystemTime::now()
+ .duration_since(SystemTime::UNIX_EPOCH)
+ .unwrap();
+ now = Some(new_now);
+ Some(new_now)
+ }
+ Some(SetTime::Time(time)) => Some(time),
+ None => None,
+ } {
+ stat.st_atime = time.as_secs() as _;
+ stat.st_atime_nsec = time.subsec_nanos() as _;
+ }
+
+ if let Some(time) = match request.mtime() {
+ Some(SetTime::Now) => match now {
+ Some(now) => Some(now),
+ None => {
+ let new_now = SystemTime::now()
+ .duration_since(SystemTime::UNIX_EPOCH)
+ .unwrap();
+ //now = Some(new_now);
+ Some(new_now)
+ }
+ },
+ Some(SetTime::Time(time)) => Some(time),
+ None => None,
+ } {
+ stat.st_mtime = time.as_secs() as _;
+ stat.st_mtime_nsec = time.subsec_nanos() as _;
+ }
+
+ if let Some(time) = request.ctime() {
+ stat.st_ctime = time.as_secs() as _;
+ stat.st_ctime_nsec = time.subsec_nanos() as _;
+ }
+
+ drop(stat);
+ Ok(file)
+}
--- /dev/null
+//! This binds the fuse file descriptor to the tokio reactor.
+
+use std::io;
+use std::os::unix::io::{AsRawFd, RawFd};
+
+use mio::event::Evented;
+use mio::unix::EventedFd;
+use mio::{Poll, PollOpt, Ready, Token};
+
+pub struct FuseFd {
+ fd: RawFd,
+}
+
+impl FuseFd {
+ pub(crate) fn from_raw(fd: RawFd) -> io::Result<Self> {
+ let this = Self { fd };
+
+ // make sure it is nonblocking
+ unsafe {
+ let rc = libc::fcntl(fd, libc::F_GETFL);
+ if rc == -1 {
+ return Err(io::Error::last_os_error());
+ }
+
+ let rc = libc::fcntl(fd, libc::F_SETFL, rc | libc::O_NONBLOCK);
+ if rc == -1 {
+ return Err(io::Error::last_os_error());
+ }
+ }
+
+ Ok(this)
+ }
+}
+
+impl AsRawFd for FuseFd {
+ #[inline]
+ fn as_raw_fd(&self) -> RawFd {
+ self.fd
+ }
+}
+
+impl Evented for FuseFd {
+ fn register(
+ &self,
+ poll: &Poll,
+ token: Token,
+ interest: Ready,
+ opts: PollOpt,
+ ) -> io::Result<()> {
+ EventedFd(&self.fd).register(poll, token, interest, opts)
+ }
+
+ fn reregister(
+ &self,
+ poll: &Poll,
+ token: Token,
+ interest: Ready,
+ opts: PollOpt,
+ ) -> io::Result<()> {
+ EventedFd(&self.fd).reregister(poll, token, interest, opts)
+ }
+
+ fn deregister(&self, poll: &Poll) -> io::Result<()> {
+ EventedFd(&self.fd).deregister(poll)
+ }
+}
--- /dev/null
+pub(crate) mod fuse_fd;
+pub mod requests;
+pub(crate) mod session;
+pub(crate) mod sys;
+pub(crate) mod util;
+
+#[doc(inline)]
+pub use sys::{EntryParam, ReplyBufState, ROOT_ID};
+
+#[doc(inline)]
+pub use requests::Request;
+
+pub use session::{Fuse, FuseSession, FuseSessionBuilder};
--- /dev/null
+//! The bigger part of the public API is about which requests we're handling:
+//!
+//! Currently all reply functions are regular functions returning an `io::Result`, however, it is
+//! possible that in the future these will become `async fns`, depending on whether the fuse file
+//! descriptor can actually fill up when it is non-blocking?
+
+use std::ffi::{CStr, CString, OsStr, OsString};
+use std::io;
+use std::os::unix::ffi::OsStrExt;
+use std::sync::Arc;
+use std::time::Duration;
+
+use crate::sys::{self, ReplyBufState};
+use crate::util::Stat;
+
+#[derive(Debug)]
+pub struct RequestGuard {
+ raw: sys::Request,
+}
+
+unsafe impl Send for RequestGuard {}
+unsafe impl Sync for RequestGuard {}
+
+impl RequestGuard {
+ pub(crate) fn from_raw(raw: sys::Request) -> Self {
+ Self { raw }
+ }
+
+ /// Consume the request and to not trigger the automatic `ENOSYS` response.
+ pub fn into_raw(mut self) -> sys::Request {
+ std::mem::replace(&mut self.raw, sys::Request::NULL)
+ }
+}
+
+impl Drop for RequestGuard {
+ fn drop(&mut self) {
+ if !self.raw.is_null() {
+ unsafe {
+ let _ = sys::fuse_reply_err(self.raw, libc::ENOSYS);
+ }
+ }
+ }
+}
+
+fn reply_err(request: RequestGuard, errno: libc::c_int) -> io::Result<()> {
+ unsafe {
+ let rc = sys::fuse_reply_err(request.into_raw(), errno);
+ if rc == 0 {
+ Ok(())
+ } else {
+ Err(io::Error::from_raw_os_error(-rc))
+ }
+ }
+}
+
+macro_rules! reply_result {
+ ($self:ident : $expr:expr) => {{
+ let rc = unsafe { $expr };
+ if rc == 0 {
+ let _done = $self.request.into_raw();
+ Ok(())
+ } else {
+ Err(io::Error::from_raw_os_error(-rc))
+ }
+ }};
+}
+
+/// Helper trait to easily provide the fail method for all the request types within the `Request`
+/// enum even after they have been moved out of the enum, without requiring the exact type.
+pub trait FuseRequest: Sized {
+ /// Send an error reply.
+ fn fail(self, errno: libc::c_int) -> io::Result<()>;
+
+ /// Convenience method to use an `io::Error` as a response.
+ fn io_fail(self, error: io::Error) -> io::Result<()> {
+ self.fail(error.raw_os_error().unwrap_or(libc::EIO))
+ }
+
+ // /// Wrap code so that `std::io::Errors` get sent as a reply and other errors (including errors
+ // /// sending the reply) will propagate through as errors.
+ // fn wrap<F, E>(self, func: F) -> io::Result<()>
+ // where
+ // F: FnOnce(Self) -> Result<(), E>,
+ // E: std::error::Error + 'static,
+ // {
+ // match func(self) {
+ // Ok(()) => Ok(()),
+ // Err(err) => {
+ // if let Some(err) = err.downcast_ref::<io::Error>() => {
+ // self.fail(err.raw_os_error().unwrap_or(libc::EIO))
+ // } else {
+ // Err(err)
+ // }
+ // }
+ // }
+ // }
+}
+
+#[derive(Debug)]
+pub enum Request {
+ Lookup(Lookup),
+ Forget(Forget),
+ Getattr(Getattr),
+ Setattr(Setattr),
+ Readdir(Readdir),
+ ReaddirPlus(ReaddirPlus),
+ Mkdir(Mkdir),
+ Create(Create),
+ Mknod(Mknod),
+ Open(Open),
+ Release(Release),
+ Read(Read),
+ Unlink(Unlink),
+ Rmdir(Rmdir),
+ Write(Write),
+ Readlink(Readlink),
+ ListXAttrSize(ListXAttrSize),
+ ListXAttr(ListXAttr),
+ GetXAttrSize(GetXAttrSize),
+ GetXAttr(GetXAttr),
+ // NOTE:
+ // Open:
+ // will need to create `FuseFileInfo.fh`, for which we'll probably add a trait generic
+ // parameter to the `Fuse` object with a `set` method which we call in the `open`
+ // *callback* already (as the `struct fuse_file_info` becomes invalid after any callback
+ // returns), and methods to get/delete the data. This will be either a generic type on
+ // `Fuse` itself, or we provide an &dyn or Box<dyn> for this.
+ // Flush: will need `FuseFileInfo.lock_owner`
+ // Poll: will need `FuseFileInfo.poll_events`
+}
+
+impl FuseRequest for Request {
+ fn fail(self, errno: libc::c_int) -> io::Result<()> {
+ match self {
+ Request::Forget(r) => {
+ r.reply();
+ Ok(())
+ }
+ Request::Lookup(r) => r.fail(errno),
+ Request::Getattr(r) => r.fail(errno),
+ Request::Setattr(r) => r.fail(errno),
+ Request::Readdir(r) => r.fail(errno),
+ Request::ReaddirPlus(r) => r.fail(errno),
+ Request::Mkdir(r) => r.fail(errno),
+ Request::Create(r) => r.fail(errno),
+ Request::Mknod(r) => r.fail(errno),
+ Request::Open(r) => r.fail(errno),
+ Request::Release(r) => r.fail(errno),
+ Request::Read(r) => r.fail(errno),
+ Request::Unlink(r) => r.fail(errno),
+ Request::Rmdir(r) => r.fail(errno),
+ Request::Write(r) => r.fail(errno),
+ Request::Readlink(r) => r.fail(errno),
+ Request::ListXAttrSize(r) => r.fail(errno),
+ Request::ListXAttr(r) => r.fail(errno),
+ Request::GetXAttrSize(r) => r.fail(errno),
+ Request::GetXAttr(r) => r.fail(errno),
+ }
+ }
+}
+
+/// A lookup for an entry in a directory. This should increase the lookup count for the inode,
+/// as from then on the kernel will refer to the looked-up entry only via the inode..
+#[derive(Debug)]
+pub struct Lookup {
+ pub(crate) request: RequestGuard,
+ pub parent: u64,
+ pub file_name: OsString,
+}
+
+impl FuseRequest for Lookup {
+ fn fail(self, errno: libc::c_int) -> io::Result<()> {
+ reply_err(self.request, errno)
+ }
+}
+
+impl Lookup {
+ pub fn reply(self, entry: &sys::EntryParam) -> io::Result<()> {
+ let rc = unsafe { sys::fuse_reply_entry(self.request.raw, Some(entry)) };
+ if rc == 0 {
+ let _done = self.request.into_raw();
+ Ok(())
+ } else {
+ Err(io::Error::from_raw_os_error(-rc))
+ }
+ }
+}
+
+/// Forget references (lookup count) for an inode. Once an inode reaches a lookup count of zero,
+/// the kernel will not refer to the inode anymore, meaning any cached information to access it may
+/// be released.
+#[derive(Debug)]
+pub struct Forget {
+ pub(crate) request: RequestGuard,
+ pub inode: u64,
+ pub count: u64,
+}
+
+impl FuseRequest for Forget {
+ /// Forget cannot fail.
+ fn fail(self, _errno: libc::c_int) -> io::Result<()> {
+ Ok(())
+ }
+}
+
+impl Forget {
+ pub fn reply(self) {
+ unsafe {
+ sys::fuse_reply_none(self.request.into_raw());
+ }
+ }
+}
+
+/// This is the equivalent of a `stat` call.
+///
+/// The inode is already known, so no changes to the lookup count occur.
+#[derive(Debug)]
+pub struct Getattr {
+ pub(crate) request: RequestGuard,
+ pub inode: u64,
+}
+
+impl FuseRequest for Getattr {
+ fn fail(self, errno: libc::c_int) -> io::Result<()> {
+ reply_err(self.request, errno)
+ }
+}
+
+impl Getattr {
+ /// Send a reply for a `Getattr` request.
+ pub fn reply(self, stat: &libc::stat, timeout: f64) -> io::Result<()> {
+ reply_result!(self: sys::fuse_reply_attr(self.request.raw, Some(stat), timeout))
+ }
+}
+
+/// Get the contents of a directory without changing any lookup counts. (Contrary to
+/// `ReaddirPlus`).
+#[derive(Debug)]
+pub struct Readdir {
+ pub(crate) request: Option<RequestGuard>,
+ pub inode: u64,
+ pub offset: i64,
+
+ reply_buffer: Option<sys::ReplyBuf>,
+}
+
+impl Drop for Readdir {
+ fn drop(&mut self) {
+ if self.reply_buffer.is_some() {
+ let _ = reply_err(self.request.take().unwrap(), libc::EIO);
+ }
+ }
+}
+
+impl FuseRequest for Readdir {
+ fn fail(mut self, errno: libc::c_int) -> io::Result<()> {
+ self.reply_buffer = None;
+ reply_err(self.request.take().unwrap(), errno)
+ }
+}
+
+impl Readdir {
+ pub(crate) fn new(request: RequestGuard, inode: u64, size: usize, offset: i64) -> Self {
+ let raw_request = request.raw;
+
+ Self {
+ request: Some(request),
+ inode,
+ offset,
+ reply_buffer: Some(sys::ReplyBuf::new(raw_request, size)),
+ }
+ }
+
+ /// Add a reply entry. Note that unless you also consume the `Readdir` object with a call to
+ /// the `reply()` method, this will produce an `EIO` error.
+ pub fn add_entry(
+ &mut self,
+ name: &OsStr,
+ stat: &libc::stat,
+ next: isize,
+ ) -> io::Result<ReplyBufState> {
+ let name = CString::new(name.as_bytes()).map_err(|_| {
+ io::Error::new(
+ io::ErrorKind::Other,
+ "tried to reply with invalid file name",
+ )
+ })?;
+
+ Ok(self
+ .reply_buffer
+ .as_mut()
+ .unwrap()
+ .add_readdir(&name, stat, next))
+ }
+
+ /// Send a successful reply. This also works if no entries have been added via `add_entry`,
+ /// indicating an empty directory without `.` or `..` present.
+ pub fn reply(mut self) -> io::Result<()> {
+ let _disable_guard = self.request.take().unwrap().into_raw();
+ self.reply_buffer.take().unwrap().reply()
+ }
+}
+
+/// Lookup all the contents of a directory. On success, the lookup count of all the returned
+/// entries should be increased by 1.
+#[derive(Debug)]
+pub struct ReaddirPlus {
+ pub(crate) request: Option<RequestGuard>,
+ pub inode: u64,
+ pub offset: i64,
+
+ reply_buffer: Option<sys::ReplyBuf>,
+}
+
+impl Drop for ReaddirPlus {
+ fn drop(&mut self) {
+ if self.reply_buffer.is_some() {
+ let _ = reply_err(self.request.take().unwrap(), libc::EIO);
+ }
+ }
+}
+
+impl FuseRequest for ReaddirPlus {
+ fn fail(mut self, errno: libc::c_int) -> io::Result<()> {
+ self.reply_buffer = None;
+ reply_err(self.request.take().unwrap(), errno)
+ }
+}
+
+impl ReaddirPlus {
+ pub(crate) fn new(request: RequestGuard, inode: u64, size: usize, offset: i64) -> Self {
+ let raw_request = request.raw;
+
+ Self {
+ request: Some(request),
+ inode,
+ offset,
+ reply_buffer: Some(sys::ReplyBuf::new(raw_request, size)),
+ }
+ }
+
+ /// Add a reply entry. Note that unless you also consume the `ReaddirPlus` object with a call
+ /// to the `reply()` method, this will produce an `EIO` error.
+ pub fn add_entry(
+ &mut self,
+ name: &OsStr,
+ stat: &libc::stat,
+ next: isize,
+ generation: u64,
+ attr_timeout: f64,
+ entry_timeout: f64,
+ ) -> io::Result<ReplyBufState> {
+ let name = CString::new(name.as_bytes()).map_err(|_| {
+ io::Error::new(
+ io::ErrorKind::Other,
+ "tried to reply with invalid file name",
+ )
+ })?;
+
+ let entry = sys::EntryParam {
+ inode: stat.st_ino,
+ generation,
+ attr: stat.clone(),
+ attr_timeout,
+ entry_timeout,
+ };
+
+ Ok(self
+ .reply_buffer
+ .as_mut()
+ .unwrap()
+ .add_readdir_plus(&name, &entry, next))
+ }
+
+ /// Send a successful reply. This also works if no entries have been added via `add_entry`,
+ /// indicating an empty directory without `.` or `..` present.
+ pub fn reply(mut self) -> io::Result<()> {
+ let _disable_guard = self.request.take().unwrap().into_raw();
+ self.reply_buffer.take().unwrap().reply()
+ }
+}
+
+/// Create a new directory with a lookup count of 1.
+#[derive(Debug)]
+pub struct Mkdir {
+ pub(crate) request: RequestGuard,
+ pub parent: u64,
+ pub dir_name: OsString,
+ pub mode: libc::mode_t,
+}
+
+impl FuseRequest for Mkdir {
+ fn fail(self, errno: libc::c_int) -> io::Result<()> {
+ reply_err(self.request, errno)
+ }
+}
+
+impl Mkdir {
+ pub fn reply(self, entry: &sys::EntryParam) -> io::Result<()> {
+ reply_result!(self: sys::fuse_reply_entry(self.request.raw, Some(entry)))
+ }
+}
+
+/// Create a new file with a lookup count of 1.
+#[derive(Debug)]
+pub struct Create {
+ pub(crate) request: RequestGuard,
+ pub parent: u64,
+ pub file_name: OsString,
+ pub mode: libc::mode_t,
+ pub(crate) file_info: sys::FuseFileInfo,
+}
+
+impl FuseRequest for Create {
+ fn fail(self, errno: libc::c_int) -> io::Result<()> {
+ reply_err(self.request, errno)
+ }
+}
+
+impl Create {
+ /// The `fh` provided here will be available in later requests for this file handle.
+ pub fn reply(mut self, entry: &sys::EntryParam, fh: u64) -> io::Result<()> {
+ self.file_info.fh = fh;
+ reply_result!(self: sys::fuse_reply_create(self.request.raw, Some(entry), &self.file_info))
+ }
+}
+
+/// Create a new node (file or device) with a lookup count of 1.
+#[derive(Debug)]
+pub struct Mknod {
+ pub(crate) request: RequestGuard,
+ pub parent: u64,
+ pub file_name: OsString,
+ pub mode: libc::mode_t,
+ pub dev: libc::dev_t,
+}
+
+impl FuseRequest for Mknod {
+ fn fail(self, errno: libc::c_int) -> io::Result<()> {
+ reply_err(self.request, errno)
+ }
+}
+
+impl Mknod {
+ /// The lookup count should be bumped by this reply.
+ pub fn reply(self, entry: &sys::EntryParam) -> io::Result<()> {
+ reply_result!(self: sys::fuse_reply_entry(self.request.raw, Some(entry)))
+ }
+}
+
+/// Open a file. This counts as one reference to the file and can be tracked separately or as part
+/// of the lookup count. If dealing with opened files requires a kind of state, the `fh` parameter
+/// on the `reply` method should point to that state, as it will be included in all requests
+/// related to this handle.
+#[derive(Debug)]
+pub struct Open {
+ pub(crate) request: RequestGuard,
+ pub inode: u64,
+ pub flags: libc::c_int,
+ pub(crate) file_info: sys::FuseFileInfo,
+}
+
+impl FuseRequest for Open {
+ fn fail(self, errno: libc::c_int) -> io::Result<()> {
+ reply_err(self.request, errno)
+ }
+}
+
+impl Open {
+ /// The `fh` provided here will be available in later requests for this file handle.
+ pub fn reply(mut self, entry: &sys::EntryParam, fh: u64) -> io::Result<()> {
+ self.file_info.fh = fh;
+ reply_result!(self: sys::fuse_reply_open(self.request.raw, Some(entry), &self.file_info))
+ }
+}
+
+/// Release a reference to a file.
+#[derive(Debug)]
+pub struct Release {
+ pub(crate) request: RequestGuard,
+ pub inode: u64,
+ pub fh: u64,
+ pub flags: libc::c_int,
+}
+
+impl FuseRequest for Release {
+ fn fail(self, errno: libc::c_int) -> io::Result<()> {
+ reply_err(self.request, errno)
+ }
+}
+
+impl Release {
+ pub fn reply(self) -> io::Result<()> {
+ reply_err(self.request, 0)
+ }
+}
+
+/// Read from a file.
+#[derive(Debug)]
+pub struct Read {
+ pub(crate) request: RequestGuard,
+ pub inode: u64,
+ pub fh: u64,
+ pub size: usize,
+ pub offset: u64,
+}
+
+impl FuseRequest for Read {
+ fn fail(self, errno: libc::c_int) -> io::Result<()> {
+ reply_err(self.request, errno)
+ }
+}
+
+impl Read {
+ pub fn reply(self, data: &[u8]) -> io::Result<()> {
+ let ptr = data.as_ptr() as *const libc::c_char;
+ reply_result!(self: sys::fuse_reply_buf(self.request.raw, ptr, data.len()))
+ }
+}
+
+pub enum SetTime {
+ /// The time should be set to the current time.
+ Now,
+
+ /// Time since the epoch.
+ Time(Duration),
+}
+
+fn c_duration(secs: libc::time_t, nsecs: i64) -> Duration {
+ Duration::new(secs as u64, nsecs as u32)
+}
+
+impl SetTime {
+ /// Truncates nsecs!
+ fn from_c(secs: libc::time_t, nsecs: i64) -> Self {
+ SetTime::Time(c_duration(secs, nsecs))
+ }
+}
+
+/// Set attributes of a file.
+#[derive(Debug)]
+pub struct Setattr {
+ pub(crate) request: RequestGuard,
+ pub inode: u64,
+ pub fh: Option<u64>,
+ pub to_set: libc::c_int,
+ pub(crate) stat: Stat,
+}
+
+impl FuseRequest for Setattr {
+ fn fail(self, errno: libc::c_int) -> io::Result<()> {
+ reply_err(self.request, errno)
+ }
+}
+
+impl Setattr {
+ /// `Some` if the mode field should be modified.
+ pub fn mode(&self) -> Option<libc::mode_t> {
+ if (self.to_set & sys::setattr::MODE) != 0 {
+ Some(self.stat.st_mode)
+ } else {
+ None
+ }
+ }
+
+ /// `Some` if the uid field should be modified.
+ pub fn uid(&self) -> Option<libc::uid_t> {
+ if (self.to_set & sys::setattr::UID) != 0 {
+ Some(self.stat.st_uid)
+ } else {
+ None
+ }
+ }
+
+ /// `Some` if the gid field should be modified.
+ pub fn gid(&self) -> Option<libc::gid_t> {
+ if (self.to_set & sys::setattr::GID) != 0 {
+ Some(self.stat.st_gid)
+ } else {
+ None
+ }
+ }
+
+ /// `Some` if the size field should be modified.
+ pub fn size(&self) -> Option<u64> {
+ if (self.to_set & sys::setattr::SIZE) != 0 {
+ Some(self.stat.st_size as u64)
+ } else {
+ None
+ }
+ }
+
+ /// `Some` if the atime field should be modified.
+ pub fn atime(&self) -> Option<SetTime> {
+ if (self.to_set & sys::setattr::ATIME) != 0 {
+ Some(SetTime::from_c(self.stat.st_atime, self.stat.st_atime_nsec))
+ } else if (self.to_set & sys::setattr::ATIME_NOW) != 0 {
+ Some(SetTime::Now)
+ } else {
+ None
+ }
+ }
+
+ /// `Some` if the mtime field should be modified.
+ pub fn mtime(&self) -> Option<SetTime> {
+ if (self.to_set & sys::setattr::MTIME) != 0 {
+ Some(SetTime::from_c(self.stat.st_mtime, self.stat.st_mtime_nsec))
+ } else if (self.to_set & sys::setattr::MTIME_NOW) != 0 {
+ Some(SetTime::Now)
+ } else {
+ None
+ }
+ }
+
+ /// `Some` if the ctime field should be modified.
+ pub fn ctime(&self) -> Option<Duration> {
+ if (self.to_set & sys::setattr::CTIME) != 0 {
+ Some(c_duration(self.stat.st_ctime, self.stat.st_ctime_nsec))
+ } else {
+ None
+ }
+ }
+
+ /// Send a reply for a `Setattr` request.
+ pub fn reply(self, stat: &libc::stat, timeout: f64) -> io::Result<()> {
+ reply_result!(self: sys::fuse_reply_attr(self.request.raw, Some(stat), timeout))
+ }
+}
+
+/// Remove a hard-link to a file. Note that the removed file may still have active references which
+/// should still be usable. This only unlinks the file from one directory.
+#[derive(Debug)]
+pub struct Unlink {
+ pub(crate) request: RequestGuard,
+ pub parent: u64,
+ pub file_name: OsString,
+}
+
+impl FuseRequest for Unlink {
+ fn fail(self, errno: libc::c_int) -> io::Result<()> {
+ reply_err(self.request, errno)
+ }
+}
+
+impl Unlink {
+ /// The `fh` provided here will be available in later requests for this file handle.
+ pub fn reply(self) -> io::Result<()> {
+ reply_err(self.request, 0)
+ }
+}
+
+/// Remove a directory entry. Note that the removed directory may still have active references
+/// which should still be usable. Only its hard link into the directory hierarchy is dropped.
+#[derive(Debug)]
+pub struct Rmdir {
+ pub(crate) request: RequestGuard,
+ pub parent: u64,
+ pub dir_name: OsString,
+}
+
+impl FuseRequest for Rmdir {
+ fn fail(self, errno: libc::c_int) -> io::Result<()> {
+ reply_err(self.request, errno)
+ }
+}
+
+impl Rmdir {
+ /// The `fh` provided here will be available in later requests for this file handle.
+ pub fn reply(self) -> io::Result<()> {
+ reply_err(self.request, 0)
+ }
+}
+
+/// Write to a file.
+#[derive(Debug)]
+pub struct Write {
+ pub(crate) request: RequestGuard,
+ pub inode: u64,
+ pub fh: u64,
+ pub data: *const u8,
+ pub size: usize,
+ pub offset: u64,
+
+ /// We keep a reference count on the buffer we pass to `fuse_session_receive_buf` so it will
+ /// not be cleared until it is used up by requests borrowing data from it, like `Write`.
+ pub(crate) buffer: Arc<sys::FuseBuf>,
+}
+
+unsafe impl Send for Write {}
+unsafe impl Sync for Write {}
+
+impl FuseRequest for Write {
+ fn fail(self, errno: libc::c_int) -> io::Result<()> {
+ reply_err(self.request, errno)
+ }
+}
+
+impl Write {
+ pub fn reply(self, size: usize) -> io::Result<()> {
+ reply_result!(self: sys::fuse_reply_write(self.request.raw, size))
+ }
+
+ #[inline]
+ pub fn data(&self) -> &[u8] {
+ unsafe { std::slice::from_raw_parts(self.data, self.size) }
+ }
+}
+
+/// Read a symbolic link.
+#[derive(Debug)]
+pub struct Readlink {
+ pub(crate) request: RequestGuard,
+ pub inode: u64,
+}
+
+impl FuseRequest for Readlink {
+ fn fail(self, errno: libc::c_int) -> io::Result<()> {
+ reply_err(self.request, errno)
+ }
+}
+
+impl Readlink {
+ pub fn reply(self, data: &OsStr) -> io::Result<()> {
+ let data = CString::new(data.as_bytes()).map_err(|_| {
+ io::Error::new(io::ErrorKind::Other, "tried to reply with invalid link")
+ })?;
+
+ self.c_reply(&data)
+ }
+
+ pub fn c_reply(self, data: &CStr) -> io::Result<()> {
+ reply_result!(self: sys::fuse_reply_readlink(self.request.raw, data.as_ptr()))
+ }
+}
+
+/// Get the size of the extended attribute list.
+#[derive(Debug)]
+pub struct ListXAttrSize {
+ pub(crate) request: RequestGuard,
+ pub inode: u64,
+}
+
+impl FuseRequest for ListXAttrSize {
+ fn fail(self, errno: libc::c_int) -> io::Result<()> {
+ reply_err(self.request, errno)
+ }
+}
+
+impl ListXAttrSize {
+ pub fn reply(self, size: usize) -> io::Result<()> {
+ reply_result!(self: sys::fuse_reply_xattr(self.request.raw, size))
+ }
+}
+
+/// List extended attributes.
+#[derive(Debug)]
+pub struct ListXAttr {
+ pub(crate) request: RequestGuard,
+ pub inode: u64,
+ pub size: usize,
+ response: Vec<u8>,
+}
+
+impl FuseRequest for ListXAttr {
+ fn fail(self, errno: libc::c_int) -> io::Result<()> {
+ reply_err(self.request, errno)
+ }
+}
+
+impl ListXAttr {
+ pub(crate) fn new(request: RequestGuard, inode: u64, size: usize) -> Self {
+ Self {
+ request,
+ inode,
+ size,
+ response: Vec::new(),
+ }
+ }
+
+ /// Check whether we can add `len` bytes to the buffer. This must already include the
+ /// terminating zero.
+ fn check_add(&self, len: usize) -> bool {
+ self.response.len() + len <= len
+ }
+
+ /// Add an extended attribute entry to the response list.
+ ///
+ /// This returns `Full` if the entry would overflow the caller's buffer, but will not fail the
+ /// request. Use `fail_full()` to send the default reply for a too-small buffer, or `reply()`
+ /// to reply with success.
+ pub fn add(&mut self, name: &OsStr) -> ReplyBufState {
+ unsafe { self.add_bytes_without_zero(name.as_bytes()) }
+ }
+
+ /// Add a raw attribute name the response list. It must not contain any zeroes.
+ ///
+ /// See `add` for details.
+ pub unsafe fn add_bytes_without_zero(&mut self, name: &[u8]) -> ReplyBufState {
+ if !self.check_add(name.len() + 1) {
+ return ReplyBufState::Full;
+ }
+
+ self.response.reserve(name.len() + 1);
+ self.response.extend(name);
+ self.response.push(0);
+
+ ReplyBufState::Ok
+ }
+
+ /// Add a raw attribute name which is already zero terminated to the response list.
+ ///
+ /// See `add` for details.
+ pub unsafe fn add_bytes_with_zero(&mut self, name: &[u8]) -> ReplyBufState {
+ if !self.check_add(name.len() + 1) {
+ return ReplyBufState::Full;
+ }
+
+ self.response.extend(name);
+
+ ReplyBufState::Ok
+ }
+
+ /// Add a raw attribute name which is already zero terminated to the response list.
+ ///
+ /// This is the safe version as it uses a `CStr`.
+ ///
+ /// See `add` for details.
+ pub fn add_c_string(&mut self, name: &CStr) -> ReplyBufState {
+ unsafe { self.add_bytes_with_zero(name.to_bytes_with_nul()) }
+ }
+
+ /// Try to replace the current reply buffer with a raw data buffer. If the provided data is too
+ /// large it will be returned as an error.
+ pub fn set_raw_reply(&mut self, data: Vec<u8>) -> Result<(), Vec<u8>> {
+ if data.len() > self.size {
+ return Err(data);
+ }
+
+ self.response = data;
+ Ok(())
+ }
+
+ /// Reply with the standard error for a too-small size.
+ pub fn fail_full(self) -> io::Result<()> {
+ self.fail(libc::ERANGE)
+ }
+
+ /// Reply to the request with the current data buffer.
+ pub fn reply(self) -> io::Result<()> {
+ let ptr = self.response.as_ptr() as *const libc::c_char;
+ reply_result!(self: sys::fuse_reply_buf(self.request.raw, ptr, self.response.len()))
+ }
+
+ /// Reply to the request with either an error (`ERANGE` if the data doesn't fit) or with the
+ /// provided raw buffer discarding anything previously added to the reply.
+ pub fn reply_raw(self, data: &[u8]) -> io::Result<()> {
+ if data.len() > self.size {
+ return self.fail_full();
+ }
+
+ let ptr = data.as_ptr() as *const libc::c_char;
+ reply_result!(self: sys::fuse_reply_buf(self.request.raw, ptr, data.len()))
+ }
+}
+
+/// Get the size of an extended attribute.
+#[derive(Debug)]
+pub struct GetXAttrSize {
+ pub(crate) request: RequestGuard,
+ pub inode: u64,
+ pub attr_name: OsString,
+}
+
+impl FuseRequest for GetXAttrSize {
+ fn fail(self, errno: libc::c_int) -> io::Result<()> {
+ reply_err(self.request, errno)
+ }
+}
+
+impl GetXAttrSize {
+ pub fn reply(self, size: usize) -> io::Result<()> {
+ reply_result!(self: sys::fuse_reply_xattr(self.request.raw, size))
+ }
+}
+
+/// Get an extended attribute.
+#[derive(Debug)]
+pub struct GetXAttr {
+ pub(crate) request: RequestGuard,
+ pub inode: u64,
+ pub attr_name: OsString,
+ pub size: usize,
+}
+
+impl FuseRequest for GetXAttr {
+ fn fail(self, errno: libc::c_int) -> io::Result<()> {
+ reply_err(self.request, errno)
+ }
+}
+
+impl GetXAttr {
+ /// Reply to the request either with an error (`ERANGE` if the buffer doesn't fit), or with the
+ /// provided data.
+ pub fn reply(self, data: &[u8]) -> io::Result<()> {
+ if data.len() > self.size {
+ return self.fail(libc::ERANGE);
+ }
+
+ let ptr = data.as_ptr() as *const libc::c_char;
+ reply_result!(self: sys::fuse_reply_buf(self.request.raw, ptr, data.len()))
+ }
+}
--- /dev/null
+use std::cell::RefCell;
+use std::collections::VecDeque;
+use std::ffi::{CStr, CString, OsStr};
+use std::os::unix::ffi::OsStrExt;
+use std::path::Path;
+use std::pin::Pin;
+use std::ptr::{self, NonNull};
+use std::sync::Arc;
+use std::task::{Context, Poll};
+use std::{io, mem};
+
+use anyhow::{bail, format_err, Error};
+use futures::ready;
+use tokio::io::PollEvented;
+use tokio::stream::Stream;
+
+use crate::fuse_fd::FuseFd;
+use crate::requests::{self, Request, RequestGuard};
+use crate::sys;
+use crate::util::Stat;
+
+/// The default set of operations enabled when nothing else is set via the `FuseSessionBuilder`
+/// methods.
+///
+/// By default the stream can only yield `Gettattr` requests.
+pub const DEFAULT_OPERATIONS: sys::Operations = sys::Operations {
+ lookup: Some(FuseData::lookup),
+ forget: Some(FuseData::forget),
+ getattr: Some(FuseData::getattr),
+ ..sys::Operations::DEFAULT
+};
+
+struct FuseData {
+ /// We're assuming that it's possible `fuse_session_process_buf` may trigger multiple
+ /// callbacks, so we need to enqueue them all,
+ ///
+ /// This is a `RefCell` since we're implementing `Stream` and therefore can only be polled by a
+ /// single thread at a time. The requests get pushed here, and then immediately yielded by the
+ /// `Stream::poll_next()` method.
+ pending_requests: RefCell<VecDeque<Request>>,
+ fbuf: Arc<sys::FuseBuf>,
+}
+
+unsafe impl Send for FuseData {}
+unsafe impl Sync for FuseData {}
+
+impl FuseData {
+ extern "C" fn lookup(request: sys::Request, parent: u64, file_name: sys::StrPtr) {
+ let fuse_data = unsafe { &*(sys::fuse_req_userdata(request) as *mut FuseData) };
+ let file_name = unsafe { CStr::from_ptr(file_name) };
+ let file_name = OsStr::from_bytes(file_name.to_bytes()).to_owned();
+ fuse_data
+ .pending_requests
+ .borrow_mut()
+ .push_back(Request::Lookup(requests::Lookup {
+ request: RequestGuard::from_raw(request),
+ parent,
+ file_name,
+ }));
+ }
+
+ extern "C" fn forget(request: sys::Request, inode: u64, nlookup: u64) {
+ let fuse_data = unsafe { &*(sys::fuse_req_userdata(request) as *mut FuseData) };
+ fuse_data
+ .pending_requests
+ .borrow_mut()
+ .push_back(Request::Forget(requests::Forget {
+ request: RequestGuard::from_raw(request),
+ inode,
+ count: nlookup,
+ }));
+ }
+
+ extern "C" fn getattr(request: sys::Request, inode: u64, _file_info: *const sys::FuseFileInfo) {
+ let fuse_data = unsafe { &*(sys::fuse_req_userdata(request) as *mut FuseData) };
+ fuse_data
+ .pending_requests
+ .borrow_mut()
+ .push_back(Request::Getattr(requests::Getattr {
+ request: RequestGuard::from_raw(request),
+ inode,
+ }));
+ }
+
+ extern "C" fn readdir(
+ request: sys::Request,
+ inode: u64,
+ size: libc::size_t,
+ offset: libc::off_t,
+ _file_info: *const sys::FuseFileInfo,
+ ) {
+ let fuse_data = unsafe { &*(sys::fuse_req_userdata(request) as *mut FuseData) };
+ fuse_data
+ .pending_requests
+ .borrow_mut()
+ .push_back(Request::Readdir(requests::Readdir::new(
+ RequestGuard::from_raw(request),
+ inode,
+ size,
+ offset,
+ )));
+ }
+
+ extern "C" fn readdirplus(
+ request: sys::Request,
+ inode: u64,
+ size: libc::size_t,
+ offset: libc::off_t,
+ _file_info: *const sys::FuseFileInfo,
+ ) {
+ let fuse_data = unsafe { &*(sys::fuse_req_userdata(request) as *mut FuseData) };
+ fuse_data
+ .pending_requests
+ .borrow_mut()
+ .push_back(Request::ReaddirPlus(requests::ReaddirPlus::new(
+ RequestGuard::from_raw(request),
+ inode,
+ size,
+ offset,
+ )));
+ }
+
+ extern "C" fn mkdir(
+ request: sys::Request,
+ parent: u64,
+ dir_name: sys::StrPtr,
+ mode: libc::mode_t,
+ ) {
+ let fuse_data = unsafe { &*(sys::fuse_req_userdata(request) as *mut FuseData) };
+ let dir_name = unsafe { CStr::from_ptr(dir_name) };
+ let dir_name = OsStr::from_bytes(dir_name.to_bytes()).to_owned();
+ fuse_data
+ .pending_requests
+ .borrow_mut()
+ .push_back(Request::Mkdir(requests::Mkdir {
+ request: RequestGuard::from_raw(request),
+ parent,
+ dir_name,
+ mode,
+ }));
+ }
+
+ extern "C" fn create(
+ request: sys::Request,
+ parent: u64,
+ file_name: sys::StrPtr,
+ mode: libc::mode_t,
+ file_info: *const sys::FuseFileInfo,
+ ) {
+ let (fuse_data, file_info, file_name) = unsafe {
+ (
+ &*(sys::fuse_req_userdata(request) as *mut FuseData),
+ &*file_info,
+ CStr::from_ptr(file_name),
+ )
+ };
+ let file_name = OsStr::from_bytes(file_name.to_bytes()).to_owned();
+ fuse_data
+ .pending_requests
+ .borrow_mut()
+ .push_back(Request::Create(requests::Create {
+ request: RequestGuard::from_raw(request),
+ parent,
+ file_name,
+ mode,
+ file_info: file_info.clone(),
+ }));
+ }
+
+ extern "C" fn mknod(
+ request: sys::Request,
+ parent: u64,
+ file_name: sys::StrPtr,
+ mode: libc::mode_t,
+ dev: libc::dev_t,
+ ) {
+ let fuse_data = unsafe { &*(sys::fuse_req_userdata(request) as *mut FuseData) };
+ let file_name = unsafe { CStr::from_ptr(file_name) };
+ let file_name = OsStr::from_bytes(file_name.to_bytes()).to_owned();
+ fuse_data
+ .pending_requests
+ .borrow_mut()
+ .push_back(Request::Mknod(requests::Mknod {
+ request: RequestGuard::from_raw(request),
+ parent,
+ file_name,
+ mode,
+ dev,
+ }));
+ }
+
+ extern "C" fn open(request: sys::Request, inode: u64, file_info: *const sys::FuseFileInfo) {
+ let (fuse_data, file_info) = unsafe {
+ (
+ &*(sys::fuse_req_userdata(request) as *mut FuseData),
+ &*file_info,
+ )
+ };
+ fuse_data
+ .pending_requests
+ .borrow_mut()
+ .push_back(Request::Open(requests::Open {
+ request: RequestGuard::from_raw(request),
+ inode,
+ flags: file_info.flags,
+ file_info: file_info.clone(),
+ }));
+ }
+
+ extern "C" fn release(request: sys::Request, inode: u64, file_info: *const sys::FuseFileInfo) {
+ let (fuse_data, file_info) = unsafe {
+ (
+ &*(sys::fuse_req_userdata(request) as *mut FuseData),
+ &*file_info,
+ )
+ };
+ fuse_data
+ .pending_requests
+ .borrow_mut()
+ .push_back(Request::Release(requests::Release {
+ request: RequestGuard::from_raw(request),
+ inode,
+ flags: file_info.flags,
+ fh: file_info.fh,
+ }));
+ }
+
+ extern "C" fn read(
+ request: sys::Request,
+ inode: u64,
+ size: libc::size_t,
+ offset: libc::off_t,
+ file_info: *const sys::FuseFileInfo,
+ ) {
+ let (fuse_data, file_info) = unsafe {
+ (
+ &*(sys::fuse_req_userdata(request) as *mut FuseData),
+ &*file_info,
+ )
+ };
+ let size = usize::from(size);
+ let offset = offset as u64;
+ fuse_data
+ .pending_requests
+ .borrow_mut()
+ .push_back(Request::Read(requests::Read {
+ request: RequestGuard::from_raw(request),
+ fh: file_info.fh,
+ inode,
+ size,
+ offset,
+ }));
+ }
+
+ extern "C" fn readlink(request: sys::Request, inode: u64) {
+ let fuse_data = unsafe { &*(sys::fuse_req_userdata(request) as *mut FuseData) };
+ fuse_data
+ .pending_requests
+ .borrow_mut()
+ .push_back(Request::Readlink(requests::Readlink {
+ request: RequestGuard::from_raw(request),
+ inode,
+ }));
+ }
+
+ extern "C" fn setattr(
+ request: sys::Request,
+ inode: u64,
+ stat: *const libc::stat,
+ to_set: libc::c_int,
+ file_info: *const sys::FuseFileInfo,
+ ) {
+ let (fuse_data, stat, file_info) = unsafe {
+ (
+ &*(sys::fuse_req_userdata(request) as *mut FuseData),
+ &*stat,
+ if file_info.is_null() {
+ None
+ } else {
+ Some(&*file_info)
+ },
+ )
+ };
+ fuse_data
+ .pending_requests
+ .borrow_mut()
+ .push_back(Request::Setattr(requests::Setattr {
+ request: RequestGuard::from_raw(request),
+ inode,
+ to_set,
+ stat: Stat::from(stat.clone()),
+ fh: file_info.map(|fi| fi.fh),
+ }));
+ }
+
+ extern "C" fn unlink(request: sys::Request, parent: u64, file_name: sys::StrPtr) {
+ let fuse_data = unsafe { &*(sys::fuse_req_userdata(request) as *mut FuseData) };
+ let file_name = unsafe { CStr::from_ptr(file_name) };
+ let file_name = OsStr::from_bytes(file_name.to_bytes()).to_owned();
+ fuse_data
+ .pending_requests
+ .borrow_mut()
+ .push_back(Request::Unlink(requests::Unlink {
+ request: RequestGuard::from_raw(request),
+ parent,
+ file_name,
+ }));
+ }
+
+ extern "C" fn rmdir(request: sys::Request, parent: u64, dir_name: sys::StrPtr) {
+ let fuse_data = unsafe { &*(sys::fuse_req_userdata(request) as *mut FuseData) };
+ let dir_name = unsafe { CStr::from_ptr(dir_name) };
+ let dir_name = OsStr::from_bytes(dir_name.to_bytes()).to_owned();
+ fuse_data
+ .pending_requests
+ .borrow_mut()
+ .push_back(Request::Rmdir(requests::Rmdir {
+ request: RequestGuard::from_raw(request),
+ parent,
+ dir_name,
+ }));
+ }
+
+ extern "C" fn write(
+ request: sys::Request,
+ inode: u64,
+ buffer: *const u8,
+ size: libc::size_t,
+ offset: libc::off_t,
+ file_info: *const sys::FuseFileInfo,
+ ) {
+ let (fuse_data, file_info) = unsafe {
+ (
+ &*(sys::fuse_req_userdata(request) as *mut FuseData),
+ &*file_info,
+ )
+ };
+ let size = usize::from(size);
+ let offset = offset as u64;
+ fuse_data
+ .pending_requests
+ .borrow_mut()
+ .push_back(Request::Write(requests::Write {
+ request: RequestGuard::from_raw(request),
+ fh: file_info.fh,
+ inode,
+ data: buffer,
+ size,
+ offset,
+ buffer: Arc::clone(&fuse_data.fbuf),
+ }));
+ }
+
+ extern "C" fn listxattr(request: sys::Request, inode: u64, size: libc::size_t) {
+ let fuse_data = unsafe { &*(sys::fuse_req_userdata(request) as *mut FuseData) };
+ let size = usize::from(size);
+ fuse_data.pending_requests.borrow_mut().push_back({
+ if size == 0 {
+ Request::ListXAttrSize(requests::ListXAttrSize {
+ request: RequestGuard::from_raw(request),
+ inode,
+ })
+ } else {
+ Request::ListXAttr(requests::ListXAttr::new(
+ RequestGuard::from_raw(request),
+ inode,
+ size,
+ ))
+ }
+ });
+ }
+
+ extern "C" fn getxattr(
+ request: sys::Request,
+ inode: u64,
+ attr_name: sys::StrPtr,
+ size: libc::size_t,
+ ) {
+ let fuse_data = unsafe { &*(sys::fuse_req_userdata(request) as *mut FuseData) };
+ let attr_name = unsafe { CStr::from_ptr(attr_name) };
+ let attr_name = OsStr::from_bytes(attr_name.to_bytes()).to_owned();
+ let size = usize::from(size);
+ fuse_data.pending_requests.borrow_mut().push_back({
+ if size == 0 {
+ Request::GetXAttrSize(requests::GetXAttrSize {
+ request: RequestGuard::from_raw(request),
+ inode,
+ attr_name,
+ })
+ } else {
+ Request::GetXAttr(requests::GetXAttr {
+ request: RequestGuard::from_raw(request),
+ inode,
+ attr_name,
+ size,
+ })
+ }
+ });
+ }
+}
+
+pub struct FuseSessionBuilder {
+ args: Vec<CString>,
+ has_debug: bool,
+ operations: sys::Operations,
+}
+
+impl FuseSessionBuilder {
+ pub fn options(self, option: &str) -> Result<Self, Error> {
+ Ok(self.options_c(
+ CString::new(option).map_err(|err| format_err!("bad option string: {}", err))?,
+ ))
+ }
+
+ pub fn options_os(self, option: &OsStr) -> Result<Self, Error> {
+ Ok(self.options_c(
+ CString::new(option.as_bytes())
+ .map_err(|err| format_err!("bad option string: {}", err))?,
+ ))
+ }
+
+ pub fn options_c(mut self, option: CString) -> Self {
+ self.args.reserve(2);
+ self.args.push(CString::new("-o").unwrap());
+ self.args.push(option);
+ self
+ }
+
+ pub fn debug(mut self) -> Self {
+ if !self.has_debug {
+ self.args.push(CString::new("--debug").unwrap());
+ }
+ self
+ }
+
+ pub fn build(self) -> Result<FuseSession, Error> {
+ let args: Vec<*const libc::c_char> = self.args.iter().map(|cstr| cstr.as_ptr()).collect();
+
+ let fuse_data = Box::new(FuseData {
+ pending_requests: RefCell::new(VecDeque::new()),
+ fbuf: Arc::new(sys::FuseBuf::new()),
+ });
+ let session = unsafe {
+ sys::fuse_session_new(
+ Some(&sys::FuseArgs::from(&args[..])),
+ Some(&self.operations),
+ mem::size_of_val(&self.operations),
+ fuse_data.as_ref() as *const FuseData as sys::ConstPtr,
+ )
+ };
+ drop(args);
+
+ if session.is_null() {
+ bail!("failed to create fuse session");
+ }
+
+ Ok(FuseSession {
+ session,
+ fuse_data: Some(fuse_data),
+ mounted: false,
+ })
+ }
+
+ /// Enable `Readdir` requests.
+ pub fn enable_readdir(mut self) -> Self {
+ self.operations.readdir = Some(FuseData::readdir);
+ self
+ }
+
+ /// Enables all of `ReaddirPlus`, `Lookup` and `Forget` requests.
+ ///
+ /// The `Lookup` and `Forget` requests are required for reference counting implied by
+ /// `ReaddirPlus`. The kernel should send `Forget` requests for references created via
+ /// `ReaddirPlus`. Not handling them wouldn't make much sense.
+ pub fn enable_readdirplus(mut self) -> Self {
+ self.operations.readdirplus = Some(FuseData::readdirplus);
+ self
+ }
+
+ /// Enable `Mkdir` requests.
+ ///
+ /// Note that the lookup count of newly created directory should be 1.
+ pub fn enable_mkdir(mut self) -> Self {
+ self.operations.mkdir = Some(FuseData::mkdir);
+ self
+ }
+
+ /// Enable `Create`, `Open` and `Release` requests.
+ ///
+ /// Create and open a file.
+ pub fn enable_create(mut self) -> Self {
+ self.operations.create = Some(FuseData::create);
+ self.enable_open()
+ }
+
+ /// Enable `Mknod`.
+ ///
+ /// This may be used by the kernel instead of `Create`.
+ pub fn enable_mknod(mut self) -> Self {
+ self.operations.mknod = Some(FuseData::mknod);
+ self
+ }
+
+ /// Enable `Open` requests.
+ ///
+ /// Open a file.
+ pub fn enable_open(mut self) -> Self {
+ self.operations.open = Some(FuseData::open);
+ self.operations.release = Some(FuseData::release);
+ self
+ }
+
+ /// Enable `Setattr` requests.
+ pub fn enable_setattr(mut self) -> Self {
+ self.operations.setattr = Some(FuseData::setattr);
+ self
+ }
+
+ /// Enable `Unlink` requests.
+ pub fn enable_unlink(mut self) -> Self {
+ self.operations.unlink = Some(FuseData::unlink);
+ self
+ }
+
+ /// Enable `Rmdir` requests.
+ pub fn enable_rmdir(mut self) -> Self {
+ self.operations.rmdir = Some(FuseData::rmdir);
+ self
+ }
+
+ /// Enable `Read` requests.
+ pub fn enable_read(mut self) -> Self {
+ self.operations.read = Some(FuseData::read);
+ self
+ }
+
+ /// Enable `Write` requests.
+ pub fn enable_write(mut self) -> Self {
+ self.operations.write = Some(FuseData::write);
+ self
+ }
+
+ /// Enable `Readlink` requests.
+ pub fn enable_readlink(mut self) -> Self {
+ self.operations.readlink = Some(FuseData::readlink);
+ self
+ }
+
+ /// Enable requests to list extended attributes:
+ ///
+ /// * `ListXAttrSize`
+ /// * `ListXAttr`
+ /// * `GetXAttrSize`
+ /// * `GetXAttr`
+ pub fn enable_read_xattr(mut self) -> Self {
+ self.operations.listxattr = Some(FuseData::listxattr);
+ self.operations.getxattr = Some(FuseData::getxattr);
+ self
+ }
+}
+
+pub struct FuseSession {
+ session: sys::MutPtr,
+ fuse_data: Option<Box<FuseData>>,
+ mounted: bool,
+}
+
+impl Drop for FuseSession {
+ fn drop(&mut self) {
+ unsafe {
+ if self.mounted {
+ let _ = sys::fuse_session_unmount(self.session);
+ }
+
+ if !self.session.is_null() {
+ let _ = sys::fuse_session_destroy(self.session);
+ }
+ }
+ }
+}
+
+impl FuseSession {
+ pub fn mount(mut self, mountpoint: &Path) -> Result<Fuse, Error> {
+ let mountpoint = mountpoint.canonicalize()?;
+ let mountpoint = CString::new(mountpoint.as_os_str().as_bytes())
+ .map_err(|err| format_err!("bad path for mount point: {}", err))?;
+
+ let rc = unsafe { sys::fuse_session_mount(self.session, mountpoint.as_ptr()) };
+ if rc != 0 {
+ bail!("mount failed");
+ }
+ self.mounted = true;
+
+ let fd = unsafe { sys::fuse_session_fd(self.session) };
+ if fd < 0 {
+ bail!("failed to get fuse session file descriptor");
+ }
+
+ let fuse_fd = PollEvented::new(FuseFd::from_raw(fd)?)?;
+
+ // disable mount guard
+ self.mounted = false;
+ Ok(Fuse {
+ session: SessionPtr(unsafe {
+ NonNull::new_unchecked(mem::replace(&mut self.session, ptr::null_mut()))
+ }),
+ fuse_data: self.fuse_data.take().unwrap(),
+ fuse_fd,
+ })
+ }
+}
+
+/// Wrap only the session pointer so we can catch auto-trait impl failures for cfuse_data and
+/// fuse_fd.
+struct SessionPtr(NonNull<libc::c_void>);
+
+impl SessionPtr {
+ #[inline]
+ fn as_ptr(&self) -> sys::MutPtr {
+ (self.0).as_ptr()
+ }
+}
+
+unsafe impl Send for SessionPtr {}
+unsafe impl Sync for SessionPtr {}
+
+/// A mounted fuse file system.
+pub struct Fuse {
+ session: SessionPtr,
+ fuse_data: Box<FuseData>,
+ fuse_fd: PollEvented<FuseFd>,
+}
+
+// We lose these via the raw session pointer:
+impl Unpin for Fuse {}
+
+impl Drop for Fuse {
+ fn drop(&mut self) {
+ unsafe {
+ let _ = sys::fuse_session_unmount(self.session.as_ptr());
+ let _ = sys::fuse_session_destroy(self.session.as_ptr());
+ }
+ }
+}
+
+impl Fuse {
+ pub fn builder(name: &str) -> Result<FuseSessionBuilder, Error> {
+ let name = CString::new(name).map_err(|err| format_err!("bad name: {}", err))?;
+
+ Ok(FuseSessionBuilder {
+ args: vec![name],
+ has_debug: false,
+ operations: DEFAULT_OPERATIONS,
+ })
+ }
+}
+
+impl Stream for Fuse {
+ type Item = io::Result<Request>;
+
+ fn poll_next(self: Pin<&mut Self>, cx: &mut Context) -> Poll<Option<Self::Item>> {
+ let this = self.get_mut();
+ loop {
+ if let Some(request) = this.fuse_data.pending_requests.borrow_mut().pop_front() {
+ return Poll::Ready(Some(Ok(request)));
+ }
+
+ ready!(this.fuse_fd.poll_read_ready(cx, mio::Ready::readable()))?;
+
+ let buf: &mut sys::FuseBuf = match Arc::get_mut(&mut this.fuse_data.fbuf) {
+ Some(buf) => buf,
+ None => {
+ this.fuse_data.fbuf = Arc::new(sys::FuseBuf::new());
+ // we literally just did Arc::new()
+ Arc::get_mut(&mut this.fuse_data.fbuf).unwrap()
+ }
+ };
+
+ let rc = unsafe { sys::fuse_session_receive_buf(this.session.as_ptr(), Some(buf)) };
+
+ if rc == -libc::EAGAIN {
+ match this.fuse_fd.clear_read_ready(cx, mio::Ready::readable()) {
+ Ok(()) => continue,
+ Err(err) => return Poll::Ready(Some(Err(err))),
+ }
+ } else if rc < 0 {
+ return Poll::Ready(Some(Err(io::Error::from_raw_os_error(-rc))));
+ }
+
+ unsafe {
+ sys::fuse_session_process_buf(this.session.as_ptr(), Some(&buf));
+ }
+ // and try again:
+ }
+ }
+}
--- /dev/null
+//! libfuse3 bindings
+
+use std::ffi::CStr;
+use std::io;
+use std::marker::PhantomData;
+
+use libc::{c_char, c_int, c_void, off_t, size_t};
+
+/// Node ID of the root i-node. This is fixed according to the FUSE API.
+pub const ROOT_ID: u64 = 1;
+
+/// FFI types for easier readability
+pub type RawRequest = *mut c_void;
+pub type MutPtr = *mut c_void;
+pub type ConstPtr = *const c_void;
+pub type StrPtr = *const c_char;
+pub type MutStrPtr = *mut c_char;
+
+/// To help us out with auto-trait implementations:
+#[derive(Clone, Copy, Debug)]
+#[repr(transparent)]
+pub struct Request {
+ raw: RawRequest,
+}
+
+impl Request {
+ pub const NULL: Self = Self {
+ raw: std::ptr::null_mut(),
+ };
+
+ #[inline]
+ pub fn is_null(&self) -> bool {
+ self.raw.is_null()
+ }
+}
+
+unsafe impl Send for Request {}
+unsafe impl Sync for Request {}
+
+/// Command line arguments passed to fuse.
+#[repr(C)]
+#[derive(Debug)]
+pub struct FuseArgs<'a> {
+ argc: c_int,
+ argv: *const StrPtr,
+ allocated: c_int,
+ _phantom: PhantomData<&'a [*const StrPtr]>,
+}
+
+impl<'a> From<&'a [*const c_char]> for FuseArgs<'a> {
+ fn from(slice: &[*const c_char]) -> Self {
+ Self {
+ argc: slice.len() as c_int,
+ argv: slice.as_ptr(),
+ allocated: 0,
+ _phantom: PhantomData,
+ }
+ }
+}
+
+#[rustfmt::skip]
+#[link(name = "fuse3")]
+extern "C" {
+ pub fn fuse_session_new(args: Option<&FuseArgs>, oprs: Option<&Operations>, size: size_t, op: ConstPtr) -> MutPtr;
+ pub fn fuse_session_fd(session: ConstPtr) -> c_int;
+ pub fn fuse_session_mount(session: ConstPtr, mountpoint: StrPtr) -> c_int;
+ pub fn fuse_session_unmount(session: ConstPtr);
+ pub fn fuse_session_destroy(session: ConstPtr);
+ pub fn fuse_reply_attr(req: Request, attr: Option<&libc::stat>, timeout: f64) -> c_int;
+ pub fn fuse_reply_err(req: Request, errno: c_int) -> c_int;
+ pub fn fuse_reply_buf(req: Request, buf: *const c_char, size: size_t) -> c_int;
+ pub fn fuse_reply_entry(req: Request, entry: Option<&EntryParam>) -> c_int;
+ pub fn fuse_reply_create(req: Request, entry: Option<&EntryParam>, file_info: *const FuseFileInfo) -> c_int;
+ pub fn fuse_reply_open(req: Request, entry: Option<&EntryParam>, file_info: *const FuseFileInfo) -> c_int;
+ pub fn fuse_reply_xattr(req: Request, size: size_t) -> c_int;
+ pub fn fuse_reply_readlink(req: Request, link: StrPtr) -> c_int;
+ pub fn fuse_reply_none(req: Request);
+ pub fn fuse_reply_write(req: Request, count: libc::size_t) -> c_int;
+ pub fn fuse_req_userdata(req: Request) -> MutPtr;
+ pub fn fuse_add_direntry_plus(req: Request, buf: MutStrPtr, bufsize: size_t, name: StrPtr, stbuf: Option<&EntryParam>, off: c_int) -> size_t;
+ pub fn fuse_add_direntry(req: Request, buf: MutStrPtr, bufsize: size_t, name: StrPtr, stbuf: Option<&libc::stat>, off: c_int) -> size_t;
+ pub fn fuse_session_process_buf(session: ConstPtr, buf: Option<&FuseBuf>);
+ pub fn fuse_session_receive_buf(session: ConstPtr, buf: Option<&mut FuseBuf>) -> c_int;
+}
+
+// Generate a `const Operations::DEFAULT` we can use as `..DEFAULT` when not implementing every
+// single call.
+macro_rules! default_to_none {
+ (
+ $(#[$attr:meta])*
+ pub struct $name:ident { $(pub $field:ident : $ty:ty,)* }
+ ) => (
+ $(#[$attr])*
+ pub struct $name {
+ $(pub $field : $ty,)*
+ }
+
+ impl $name {
+ pub const DEFAULT: Self = Self {
+ $($field : None,)*
+ };
+ }
+ );
+}
+
+#[rustfmt::skip]
+default_to_none! {
+ /// `Operations` defines the callback function table of supported operations.
+ #[repr(C)]
+ #[derive(Default)]
+ pub struct Operations {
+ // The order in which the functions are listed matters, as the offset in the
+ // struct defines what function the fuse driver uses.
+ // It should therefore not be altered!
+ pub init: Option<extern fn(userdata: MutPtr)>,
+ pub destroy: Option<extern fn(userdata: MutPtr)>,
+ pub lookup: Option<extern fn(req: Request, parent: u64, name: StrPtr)>,
+ pub forget: Option<extern fn(req: Request, inode: u64, nlookup: u64)>,
+ pub getattr: Option<extern fn(req: Request, inode: u64, file_info: *const FuseFileInfo)>,
+ pub setattr: Option<extern fn(req: Request, inode: u64, attr: *const libc::stat, to_set: c_int, file_info: *const FuseFileInfo)>,
+ pub readlink: Option<extern fn(req: Request, inode: u64)>,
+ pub mknod: Option<extern fn(req: Request, parent: u64, name: StrPtr, mode: libc::mode_t, rdev: libc::dev_t)>,
+ pub mkdir: Option<extern fn(req: Request, parent: u64, name: StrPtr, mode: libc::mode_t)>,
+ pub unlink: Option<extern fn(req: Request, parent: u64, name: StrPtr)>,
+ pub rmdir: Option<extern fn(req: Request, parent: u64, name: StrPtr)>,
+ pub symlink: Option<extern fn(req: Request, link: StrPtr, parent: u64, name: StrPtr)>,
+ pub rename: Option<extern fn(req: Request, parent: u64, name: StrPtr, newparent: u64, newname: StrPtr, flags: c_int)>,
+ pub link: Option<extern fn(req: Request, inode: u64, newparent: u64, newname: StrPtr)>,
+ pub open: Option<extern fn(req: Request, inode: u64, file_info: *const FuseFileInfo)>,
+ pub read: Option<extern fn(req: Request, inode: u64, size: size_t, offset: libc::off_t, file_info: *const FuseFileInfo)>,
+ pub write: Option<extern fn(req: Request, inode: u64, buffer: *const u8, size: size_t, offset: libc::off_t, file_info: *const FuseFileInfo)>,
+ pub flush: Option<extern fn(req: Request, inode: u64, file_info: *const FuseFileInfo)>,
+ pub release: Option<extern fn(req: Request, inode: u64, file_info: *const FuseFileInfo)>,
+ pub fsync: Option<extern fn(req: Request, inode: u64, datasync: c_int, file_info: *const FuseFileInfo)>,
+ pub opendir: Option<extern fn(req: Request, inode: u64, file_info: *const FuseFileInfo)>,
+ pub readdir: Option<extern fn(req: Request, inode: u64, size: size_t, offset: off_t, file_info: *const FuseFileInfo)>,
+ pub releasedir: Option<extern fn(req: Request, inode: u64, file_info: *const FuseFileInfo)>,
+ pub fsyncdir: Option<extern fn(req: Request, inode: u64, datasync: c_int, file_info: *const FuseFileInfo)>,
+ pub statfs: Option<extern fn(req: Request, inode: u64)>,
+ pub setxattr: Option<extern fn(req: Request, inode: u64, name: StrPtr, value: StrPtr, size: size_t, flags: c_int)>,
+ pub getxattr: Option<extern fn(req: Request, inode: u64, name: StrPtr, size: size_t)>,
+ pub listxattr: Option<extern fn(req: Request, inode: u64, size: size_t)>,
+ pub removexattr: Option<extern fn(req: Request, inode: u64, name: StrPtr)>,
+ pub access: Option<extern fn(req: Request, inode: u64, mask: i32)>,
+ pub create: Option<extern fn(req: Request, parent: u64, name: StrPtr, mode: libc::mode_t, file_info: *const FuseFileInfo)>,
+ pub getlk: Option<extern fn(req: Request, inode: u64, file_info: *const FuseFileInfo, lock: MutPtr)>,
+ pub setlk: Option<extern fn(req: Request, inode: u64, file_info: *const FuseFileInfo, lock: MutPtr, sleep: c_int)>,
+ pub bmap: Option<extern fn(req: Request, inode: u64, blocksize: size_t, idx: u64)>,
+ pub ioctl: Option<extern fn(req: Request, inode: u64, cmd: c_int, arg: MutPtr, file_info: *const FuseFileInfo, flags: c_int, in_buf: ConstPtr, in_bufsz: size_t, out_bufsz: size_t)>,
+ pub poll: Option<extern fn(req: Request, inode: u64, file_info: *const FuseFileInfo, pollhandle: MutPtr)>,
+ pub write_buf: Option<extern fn(req: Request, inode: u64, bufv: MutPtr, offset: libc::off_t, file_info: *const FuseFileInfo)>,
+ pub retrieve_reply: Option<extern fn(req: Request, cookie: ConstPtr, inode: u64, offset: libc::off_t, bufv: MutPtr)>,
+ pub forget_multi: Option<extern fn(req: Request, count: size_t, forgets: MutPtr)>,
+ pub flock: Option<extern fn(req: Request, inode: u64, file_info: *const FuseFileInfo, op: c_int)>,
+ pub fallocate: Option<extern fn(req: Request, inode: u64, mode: c_int, offset: libc::off_t, length: libc::off_t, file_info: *const FuseFileInfo)>,
+ pub readdirplus: Option<extern fn(req: Request, inode: u64, size: size_t, offset: off_t, file_info: *const FuseFileInfo)>,
+ pub copy_file_range: Option<extern fn(req: Request, ino_in: u64, off_in: libc::off_t, fi_in: *const FuseFileInfo, ino_out: u64, off_out: libc::off_t, fi_out: *const FuseFileInfo, len: size_t, flags: c_int)>,
+ }
+}
+
+/// FUSE entry for fuse_reply_entry in lookup callback
+#[repr(C)]
+pub struct EntryParam {
+ pub inode: u64,
+ pub generation: u64,
+ pub attr: libc::stat,
+ pub attr_timeout: f64,
+ pub entry_timeout: f64,
+}
+
+impl EntryParam {
+ /// A simple entry has a maximum attribute/entry timeout value and always a generatio of 1.
+ /// This is a convenience method used since we mostly use this for static unchangable archives.
+ pub fn simple(inode: u64, attr: libc::stat) -> Self {
+ Self {
+ inode,
+ generation: 1,
+ attr,
+ attr_timeout: std::f64::MAX,
+ entry_timeout: std::f64::MAX,
+ }
+ }
+}
+
+#[derive(Debug)]
+#[repr(C)]
+pub struct FuseBuf {
+ /// Size of data in bytes
+ size: size_t,
+
+ /// Buffer flags
+ flags: c_int,
+
+ /// Memory pointer
+ ///
+ /// Used unless FUSE_BUF_IS_FD flag is set.
+ mem: *mut c_void,
+
+ /// File descriptor
+ ///
+ /// Used if FUSE_BUF_IS_FD flag is set.
+ fd: c_int,
+
+ /// File position
+ ///
+ /// Used if FUSE_BUF_FD_SEEK flag is set.
+ pos: off_t,
+}
+
+impl Drop for FuseBuf {
+ fn drop(&mut self) {
+ unsafe {
+ libc::free(self.mem);
+ }
+ }
+}
+
+impl FuseBuf {
+ pub fn new() -> Self {
+ unsafe { std::mem::zeroed() }
+ }
+}
+
+#[derive(Clone, Debug)]
+#[repr(C)]
+pub struct FuseFileInfo {
+ /// Open flags. Available in open() and release()
+ pub flags: c_int,
+
+ /// Various bitfields which we will not support for now:
+ _bits: u64,
+
+ /// File handle. May be filled in by filesystem in open().
+ /// Available in all other file operations
+ pub fh: u64,
+
+ /// Lock owner id. Available in locking operations and flush.
+ pub lock_owner: u64,
+
+ /// Requested poll events. Available in ->poll. Only set on kernels
+ /// which support it. If unsupported, this field is set to zero.
+ pub poll_events: u32,
+}
+
+#[rustfmt::skip]
+pub mod setattr {
+ pub const MODE : libc::c_int = 1 << 0;
+ pub const UID : libc::c_int = 1 << 1;
+ pub const GID : libc::c_int = 1 << 2;
+ pub const SIZE : libc::c_int = 1 << 3;
+ pub const ATIME : libc::c_int = 1 << 4;
+ pub const MTIME : libc::c_int = 1 << 5;
+ pub const ATIME_NOW : libc::c_int = 1 << 7;
+ pub const MTIME_NOW : libc::c_int = 1 << 8;
+ pub const CTIME : libc::c_int = 1 << 10;
+}
+
+/// State of ReplyBuf after last add_entry call
+#[must_use]
+pub enum ReplyBufState {
+ /// Entry was successfully added to ReplyBuf
+ Ok,
+ /// Entry did not fit into ReplyBuf, was not added
+ Full,
+}
+
+impl ReplyBufState {
+ #[inline]
+ pub fn is_full(self) -> bool {
+ match self {
+ ReplyBufState::Full => true,
+ _ => false,
+ }
+ }
+}
+
+/// Used to correctly fill and reply the buffer for the readdirplus callback
+pub struct ReplyBuf {
+ /// internal buffer holding the binary data
+ buffer: Vec<u8>,
+ /// offset up to which the buffer is filled already
+ filled: usize,
+ /// fuse request the buffer is used to reply to
+ request: Request,
+}
+
+impl std::fmt::Debug for ReplyBuf {
+ fn fmt(&self, _f: &mut std::fmt::Formatter) -> std::fmt::Result {
+ Ok(())
+ }
+}
+
+impl ReplyBuf {
+ /// Create a new empty `ReplyBuf` of `size` with element counting index at `next`.
+ pub fn new(request: Request, size: usize) -> Self {
+ let mut buffer = Vec::with_capacity(size);
+ unsafe {
+ buffer.set_len(size);
+ }
+ Self {
+ buffer,
+ filled: 0,
+ request,
+ }
+ }
+
+ /// Send the reply with what we have buffered so far.
+ pub fn reply(mut self) -> io::Result<()> {
+ let rc = unsafe {
+ let ptr = self.buffer.as_mut_ptr() as *mut c_char;
+ fuse_reply_buf(self.request, ptr, self.filled)
+ };
+ if rc == 0 {
+ Ok(())
+ } else {
+ Err(io::Error::from_raw_os_error(-rc))
+ }
+ }
+
+ fn after_add(&mut self, entry_size: usize) -> ReplyBufState {
+ let filled = self.filled + entry_size;
+
+ if filled > self.buffer.len() {
+ ReplyBufState::Full
+ } else {
+ self.filled = filled;
+ ReplyBufState::Ok
+ }
+ }
+
+ pub fn add_readdir_plus(
+ &mut self,
+ name: &CStr,
+ attr: &EntryParam,
+ next: isize,
+ ) -> ReplyBufState {
+ let size = unsafe {
+ let buffer = &mut self.buffer[self.filled..];
+ fuse_add_direntry_plus(
+ self.request,
+ buffer.as_mut_ptr() as *mut c_char,
+ buffer.len(),
+ name.as_ptr(),
+ Some(attr),
+ next as c_int,
+ ) as usize
+ };
+ self.after_add(size)
+ }
+
+ pub fn add_readdir(&mut self, name: &CStr, attr: &libc::stat, next: isize) -> ReplyBufState {
+ let size = unsafe {
+ let buffer = &mut self.buffer[self.filled..];
+ fuse_add_direntry(
+ self.request,
+ buffer.as_mut_ptr() as *mut c_char,
+ buffer.len(),
+ name.as_ptr(),
+ Some(attr),
+ next as c_int,
+ ) as usize
+ };
+ self.after_add(size)
+ }
+}
--- /dev/null
+//! Some helpers.
+
+use std::fmt;
+
+/// Helper for `Debug` derives.
+#[derive(Clone)]
+pub struct Stat {
+ stat: libc::stat,
+}
+
+impl From<libc::stat> for Stat {
+ fn from(stat: libc::stat) -> Self {
+ Self { stat }
+ }
+}
+
+impl std::ops::Deref for Stat {
+ type Target = libc::stat;
+
+ fn deref(&self) -> &Self::Target {
+ &self.stat
+ }
+}
+
+impl std::ops::DerefMut for Stat {
+ fn deref_mut(&mut self) -> &mut Self::Target {
+ &mut self.stat
+ }
+}
+
+impl fmt::Debug for Stat {
+ fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
+ // don't care much about more fields than these:
+ fmt.debug_struct("stat")
+ .field("st_ino", &self.stat.st_ino)
+ .field("st_mode", &self.stat.st_mode)
+ .field("st_uid", &self.stat.st_uid)
+ .field("st_gid", &self.stat.st_gid)
+ .field("st_rdev", &self.stat.st_rdev)
+ .field("st_size", &self.stat.st_size)
+ .field("st_ctime", &self.stat.st_ctime)
+ .field("st_mtime", &self.stat.st_mtime)
+ .field("st_atime", &self.stat.st_atime)
+ .finish()
+ }
+}