5
0
mirror of git://git.proxmox.com/git/proxmox-fuse.git synced 2024-12-21 13:34:41 +03:00
Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
This commit is contained in:
Wolfgang Bumiller 2020-05-07 10:27:08 +02:00
commit e49e2d94ce
13 changed files with 3115 additions and 0 deletions

5
.cargo/config Normal file
View File

@ -0,0 +1,5 @@
[source]
[source.debian-packages]
directory = "/usr/share/cargo/registry"
[source.crates-io]
replace-with = "debian-packages"

3
.gitignore vendored Normal file
View File

@ -0,0 +1,3 @@
/target
Cargo.lock
test

16
Cargo.toml Normal file
View File

@ -0,0 +1,16 @@
[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"] }

View File

@ -0,0 +1,241 @@
//! 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(())
}
}

419
examples/tmpfs/fs.rs Normal file
View File

@ -0,0 +1,419 @@
//! 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)
}
}

15
examples/tmpfs/macros.rs Normal file
View File

@ -0,0 +1,15 @@
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());
};
}

318
examples/tmpfs/main.rs Normal file
View File

@ -0,0 +1,318 @@
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)
}

66
src/fuse_fd.rs Normal file
View File

@ -0,0 +1,66 @@
//! 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)
}
}

13
src/lib.rs Normal file
View File

@ -0,0 +1,13 @@
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};

912
src/requests.rs Normal file
View File

@ -0,0 +1,912 @@
//! 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()))
}
}

696
src/session.rs Normal file
View File

@ -0,0 +1,696 @@
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:
}
}
}

365
src/sys.rs Normal file
View File

@ -0,0 +1,365 @@
//! 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)
}
}

46
src/util.rs Normal file
View File

@ -0,0 +1,46 @@
//! 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()
}
}