#![allow(
clippy::cast_lossless,
clippy::cast_possible_truncation,
clippy::cast_sign_loss
)]
use crate::{
io::{Reader, ReaderExt, Writer, WriterExt},
kernel::{
fuse_init_out,
FUSE_KERNEL_MINOR_VERSION,
FUSE_KERNEL_VERSION,
FUSE_MAX_PAGES,
FUSE_MIN_READ_BUFFER,
FUSE_NO_OPENDIR_SUPPORT,
FUSE_NO_OPEN_SUPPORT,
},
op::OperationKind,
session::Session,
util::{as_bytes, BuilderExt},
};
use bitflags::bitflags;
use lazy_static::lazy_static;
use std::{cmp, fmt, io};
const MINIMUM_SUPPORTED_MINOR_VERSION: u32 = 23;
const DEFAULT_MAX_WRITE: u32 = 16 * 1024 * 1024;
const MIN_MAX_WRITE: u32 = FUSE_MIN_READ_BUFFER - BUFFER_HEADER_SIZE as u32;
const MAX_MAX_PAGES: usize = 256;
const BUFFER_HEADER_SIZE: usize = 0x1000;
lazy_static! {
static ref PAGE_SIZE: usize = { unsafe { libc::sysconf(libc::_SC_PAGESIZE) as usize } };
}
#[derive(Debug)]
pub struct SessionInitializer {
max_readahead: u32,
flags: CapabilityFlags,
max_background: u16,
congestion_threshold: u16,
max_write: u32,
time_gran: u32,
max_pages: u16,
}
impl Default for SessionInitializer {
fn default() -> Self {
Self {
max_readahead: u32::max_value(),
flags: CapabilityFlags::default(),
max_background: 0,
congestion_threshold: 0,
max_write: DEFAULT_MAX_WRITE,
time_gran: 1,
max_pages: 0,
}
}
}
impl SessionInitializer {
pub fn flags(&mut self) -> &mut CapabilityFlags {
&mut self.flags
}
pub fn max_readahead(&mut self, value: u32) -> &mut Self {
self.max_readahead = value;
self
}
pub fn max_write(&mut self, value: u32) -> &mut Self {
assert!(
value >= MIN_MAX_WRITE,
"max_write must be greater or equal to {}",
MIN_MAX_WRITE,
);
self.max_write = value;
self
}
pub fn max_background(&mut self, max_background: u16) -> &mut Self {
self.max_background = max_background;
self
}
pub fn congestion_threshold(&mut self, mut threshold: u16) -> &mut Self {
assert!(
threshold <= self.max_background,
"The congestion_threshold must be less or equal to max_background"
);
if threshold == 0 {
threshold = self.max_background * 3 / 4;
tracing::debug!(congestion_threshold = threshold);
}
self.congestion_threshold = threshold;
self
}
pub fn time_gran(&mut self, time_gran: u32) -> &mut Self {
self.time_gran = time_gran;
self
}
#[doc(hidden)]
pub fn init_buf_size(&self) -> usize {
BUFFER_HEADER_SIZE + *PAGE_SIZE * MAX_MAX_PAGES
}
#[allow(clippy::cognitive_complexity)]
pub async fn try_init<I: ?Sized>(&self, io: &mut I) -> io::Result<Option<Session>>
where
I: Reader + Writer + Unpin,
{
let request = io.read_request().await?;
let header = request.header();
let arg = request.arg()?;
match arg {
OperationKind::Init { arg: init_in } => {
let capable = CapabilityFlags::from_bits_truncate(init_in.flags);
let readonly_flags = init_in.flags & !CapabilityFlags::all().bits();
tracing::debug!("INIT request:");
tracing::debug!(" proto = {}.{}:", init_in.major, init_in.minor);
tracing::debug!(" flags = 0x{:08x} ({:?})", init_in.flags, capable);
tracing::debug!(" max_readahead = 0x{:08X}", init_in.max_readahead);
tracing::debug!(" max_pages = {}", init_in.flags & FUSE_MAX_PAGES != 0);
tracing::debug!(
" no_open_support = {}",
init_in.flags & FUSE_NO_OPEN_SUPPORT != 0
);
tracing::debug!(
" no_opendir_support = {}",
init_in.flags & FUSE_NO_OPENDIR_SUPPORT != 0
);
let mut init_out = fuse_init_out::default();
init_out.major = FUSE_KERNEL_VERSION;
init_out.minor = FUSE_KERNEL_MINOR_VERSION;
if init_in.major > 7 {
tracing::debug!("wait for a second INIT request with an older version.");
io.send_msg(header.unique, 0, unsafe { as_bytes(&init_out) })
.await?;
return Ok(None);
}
if init_in.major < 7 || init_in.minor < MINIMUM_SUPPORTED_MINOR_VERSION {
tracing::warn!(
"polyfuse supports only ABI 7.{} or later. {}.{} is not supported",
MINIMUM_SUPPORTED_MINOR_VERSION,
init_in.major,
init_in.minor
);
io.send_msg(header.unique, -libc::EPROTO, &()).await?;
return Ok(None);
}
init_out.minor = cmp::min(init_out.minor, init_in.minor);
init_out.flags = (self.flags & capable).bits();
init_out.flags |= crate::kernel::FUSE_BIG_WRITES;
init_out.max_readahead = cmp::min(self.max_readahead, init_in.max_readahead);
init_out.max_write = self.max_write;
init_out.max_background = self.max_background;
init_out.congestion_threshold = self.congestion_threshold;
init_out.time_gran = self.time_gran;
if init_in.flags & FUSE_MAX_PAGES != 0 {
init_out.flags |= FUSE_MAX_PAGES;
init_out.max_pages = cmp::min(
(init_out.max_write - 1) / (*PAGE_SIZE as u32) + 1,
u16::max_value() as u32,
) as u16;
}
debug_assert_eq!(init_out.major, FUSE_KERNEL_VERSION);
debug_assert!(init_out.minor >= MINIMUM_SUPPORTED_MINOR_VERSION);
tracing::debug!("Reply to INIT:");
tracing::debug!(" proto = {}.{}:", init_out.major, init_out.minor);
tracing::debug!(
" flags = 0x{:08x} ({:?})",
init_out.flags,
CapabilityFlags::from_bits_truncate(init_out.flags)
);
tracing::debug!(" max_readahead = 0x{:08X}", init_out.max_readahead);
tracing::debug!(" max_write = 0x{:08X}", init_out.max_write);
tracing::debug!(" max_background = 0x{:04X}", init_out.max_background);
tracing::debug!(
" congestion_threshold = 0x{:04X}",
init_out.congestion_threshold
);
tracing::debug!(" time_gran = {}", init_out.time_gran);
io.send_msg(header.unique, 0, unsafe { as_bytes(&init_out) })
.await?;
init_out.flags |= readonly_flags;
let conn = ConnectionInfo(init_out);
let bufsize = BUFFER_HEADER_SIZE + conn.max_write() as usize;
Ok(Some(Session::new(conn, bufsize)))
}
_ => {
tracing::warn!(
"ignoring an operation before init (opcode={:?})",
header.opcode
);
io.send_msg(header.unique, -libc::EIO, &()).await?;
Ok(None)
}
}
}
}
pub struct ConnectionInfo(fuse_init_out);
impl fmt::Debug for ConnectionInfo {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("ConnectionInfo")
.field("proto_major", &self.proto_major())
.field("proto_minor", &self.proto_minor())
.field("flags", &self.flags())
.field("no_open_support", &self.no_open_support())
.field("no_opendir_support", &self.no_opendir_support())
.field("max_readahead", &self.max_readahead())
.field("max_write", &self.max_write())
.field("max_background", &self.max_background())
.field("congestion_threshold", &self.congestion_threshold())
.field("time_gran", &self.time_gran())
.if_some(self.max_pages(), |f, pages| f.field("max_pages", &pages))
.finish()
}
}
impl ConnectionInfo {
pub fn proto_major(&self) -> u32 {
self.0.major
}
pub fn proto_minor(&self) -> u32 {
self.0.minor
}
pub fn flags(&self) -> CapabilityFlags {
CapabilityFlags::from_bits_truncate(self.0.flags)
}
pub fn no_open_support(&self) -> bool {
self.0.flags & FUSE_NO_OPEN_SUPPORT != 0
}
pub fn no_opendir_support(&self) -> bool {
self.0.flags & FUSE_NO_OPENDIR_SUPPORT != 0
}
pub fn max_readahead(&self) -> u32 {
self.0.max_readahead
}
pub fn max_write(&self) -> u32 {
self.0.max_write
}
#[doc(hidden)]
pub fn max_background(&self) -> u16 {
self.0.max_background
}
#[doc(hidden)]
pub fn congestion_threshold(&self) -> u16 {
self.0.congestion_threshold
}
#[doc(hidden)]
pub fn time_gran(&self) -> u32 {
self.0.time_gran
}
#[doc(hidden)]
pub fn max_pages(&self) -> Option<u16> {
if self.0.flags & FUSE_MAX_PAGES != 0 {
Some(self.0.max_pages)
} else {
None
}
}
}
bitflags! {
#[repr(transparent)]
pub struct CapabilityFlags: u32 {
const ASYNC_READ = crate::kernel::FUSE_ASYNC_READ;
const ATOMIC_O_TRUNC = crate::kernel::FUSE_ATOMIC_O_TRUNC;
const AUTO_INVAL_DATA = crate::kernel::FUSE_AUTO_INVAL_DATA;
const ASYNC_DIO = crate::kernel::FUSE_ASYNC_DIO;
const PARALLEL_DIROPS = crate::kernel::FUSE_PARALLEL_DIROPS;
const HANDLE_KILLPRIV = crate::kernel::FUSE_HANDLE_KILLPRIV;
const POSIX_LOCKS = crate::kernel::FUSE_POSIX_LOCKS;
const FLOCK_LOCKS = crate::kernel::FUSE_FLOCK_LOCKS;
const EXPORT_SUPPORT = crate::kernel::FUSE_EXPORT_SUPPORT;
const DONT_MASK = crate::kernel::FUSE_DONT_MASK;
const WRITEBACK_CACHE = crate::kernel::FUSE_WRITEBACK_CACHE;
const POSIX_ACL = crate::kernel::FUSE_POSIX_ACL;
const READDIRPLUS = crate::kernel::FUSE_DO_READDIRPLUS;
const READDIRPLUS_AUTO = crate::kernel::FUSE_READDIRPLUS_AUTO;
}
}
impl Default for CapabilityFlags {
fn default() -> Self {
Self::ASYNC_READ
| Self::PARALLEL_DIROPS
| Self::AUTO_INVAL_DATA
| Self::HANDLE_KILLPRIV
| Self::ASYNC_DIO
| Self::ATOMIC_O_TRUNC
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::kernel::{fuse_in_header, fuse_init_in};
use futures::executor::block_on;
use std::mem;
#[test]
fn init_default() {
#[allow(clippy::cast_possible_truncation)]
let in_header = fuse_in_header {
len: (mem::size_of::<fuse_in_header>() + mem::size_of::<fuse_init_in>()) as u32,
opcode: crate::kernel::FUSE_INIT,
unique: 2,
nodeid: 0,
uid: 100,
gid: 100,
pid: 12,
padding: 0,
};
let init_in = fuse_init_in {
major: 7,
minor: 23,
max_readahead: 40,
flags: CapabilityFlags::all().bits()
| FUSE_MAX_PAGES
| FUSE_NO_OPEN_SUPPORT
| FUSE_NO_OPENDIR_SUPPORT,
};
let mut input = vec![];
input.extend_from_slice(unsafe { crate::util::as_bytes(&in_header) });
input.extend_from_slice(unsafe { crate::util::as_bytes(&init_in) });
let mut reader = &input[..];
let mut writer = Vec::<u8>::new();
let mut io = crate::io::unite(&mut reader, &mut writer);
let init_session = SessionInitializer::default();
let session = block_on(init_session.try_init(&mut io))
.expect("initialization failed")
.expect("empty session");
let conn = session.connection_info();
assert_eq!(conn.proto_major(), 7);
assert_eq!(conn.proto_minor(), 23);
assert_eq!(conn.max_readahead(), 40);
assert_eq!(conn.max_background(), 0);
assert_eq!(conn.congestion_threshold(), 0);
assert_eq!(conn.max_write(), DEFAULT_MAX_WRITE);
assert_eq!(
conn.max_pages(),
Some((DEFAULT_MAX_WRITE / (*PAGE_SIZE as u32)) as u16)
);
assert_eq!(conn.time_gran(), 1);
assert!(conn.no_open_support());
assert!(conn.no_opendir_support());
}
}