mirror of
git://git.proxmox.com/git/proxmox-fuse.git
synced 2024-12-21 13:34:41 +03:00
import
Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
This commit is contained in:
commit
e49e2d94ce
5
.cargo/config
Normal file
5
.cargo/config
Normal 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
3
.gitignore
vendored
Normal file
@ -0,0 +1,3 @@
|
||||
/target
|
||||
Cargo.lock
|
||||
test
|
16
Cargo.toml
Normal file
16
Cargo.toml
Normal 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"] }
|
241
examples/tmpfs/block_file.rs
Normal file
241
examples/tmpfs/block_file.rs
Normal 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
419
examples/tmpfs/fs.rs
Normal 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
15
examples/tmpfs/macros.rs
Normal 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
318
examples/tmpfs/main.rs
Normal 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
66
src/fuse_fd.rs
Normal 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
13
src/lib.rs
Normal 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
912
src/requests.rs
Normal 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
696
src/session.rs
Normal 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
365
src/sys.rs
Normal 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
46
src/util.rs
Normal 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()
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user