use std::mem;
use libc::{EIO, ENOSYS, EPROTO};
use time::Timespec;
use argument::ArgumentIterator;
use channel::ChannelSender;
use Filesystem;
use fuse::*;
use fuse::consts::*;
use fuse::fuse_opcode::*;
use reply::{Reply, ReplyRaw, ReplyEmpty, ReplyDirectory};
use session::{MAX_WRITE_SIZE, Session};
#[cfg(not(target_os = "macos"))]
const INIT_FLAGS: u32 = FUSE_ASYNC_READ | FUSE_EXPORT_SUPPORT | FUSE_BIG_WRITES;
#[cfg(target_os = "macos")]
const INIT_FLAGS: u32 = FUSE_ASYNC_READ | FUSE_EXPORT_SUPPORT | FUSE_BIG_WRITES | FUSE_CASE_INSENSITIVE | FUSE_VOL_RENAME | FUSE_XTIMES;
pub fn request<'a> (ch: ChannelSender, buffer: &'a [u8]) -> Option<Request<'a>> {
Request::new(ch, buffer)
}
pub fn dispatch<FS: Filesystem> (req: &Request, se: &mut Session<FS>) {
req.dispatch(se);
}
#[derive(Debug)]
pub struct Request<'a> {
ch: ChannelSender,
header: &'a fuse_in_header,
data: &'a [u8],
}
impl<'a> Request<'a> {
fn new (ch: ChannelSender, buffer: &'a [u8]) -> Option<Request<'a>> {
if buffer.len() < mem::size_of::<fuse_in_header>() {
error!("Short read of FUSE request ({} < {})", buffer.len(), mem::size_of::<fuse_in_header>());
return None;
}
let mut data = ArgumentIterator::new(buffer);
let req = Request {
ch: ch,
header: data.fetch(),
data: data.fetch_data(),
};
if buffer.len() < req.header.len as usize {
error!("Short read of FUSE request ({} < {})", buffer.len(), req.header.len);
return None;
}
Some(req)
}
fn dispatch<FS: Filesystem> (&self, se: &mut Session<FS>) {
let opcode = match fuse_opcode::from_u32(self.header.opcode) {
Some(op) => op,
None => {
warn!("Ignoring unknown FUSE operation {}", self.header.opcode);
self.reply::<ReplyEmpty>().error(ENOSYS);
return;
},
};
let mut data = ArgumentIterator::new(self.data);
match opcode {
FUSE_INIT => {
let reply: ReplyRaw<fuse_init_out> = self.reply();
let arg: &fuse_init_in = data.fetch();
debug!("INIT({}) kernel: ABI {}.{}, flags {:#x}, max readahead {}", self.header.unique, arg.major, arg.minor, arg.flags, arg.max_readahead);
if arg.major < 7 || (arg.major == 7 && arg.minor < 6) {
error!("Unsupported FUSE ABI version {}.{}", arg.major, arg.minor);
reply.error(EPROTO);
return;
}
se.proto_major = arg.major;
se.proto_minor = arg.minor;
let res = se.filesystem.init(self);
if let Err(err) = res {
reply.error(err);
return;
}
let init = fuse_init_out {
major: FUSE_KERNEL_VERSION,
minor: FUSE_KERNEL_MINOR_VERSION,
max_readahead: arg.max_readahead, flags: arg.flags & INIT_FLAGS, unused: 0,
max_write: MAX_WRITE_SIZE as u32, };
debug!("INIT({}) response: ABI {}.{}, flags {:#x}, max readahead {}, max write {}", self.header.unique, init.major, init.minor, init.flags, init.max_readahead, init.max_write);
se.initialized = true;
reply.ok(&init);
},
_ if !se.initialized => {
warn!("Ignoring FUSE operation {} before init", self.header.opcode);
self.reply::<ReplyEmpty>().error(EIO);
},
FUSE_DESTROY => {
debug!("DESTROY({})", self.header.unique);
se.filesystem.destroy(self);
se.destroyed = true;
self.reply::<ReplyEmpty>().ok();
}
_ if se.destroyed => {
warn!("Ignoring FUSE operation {} after destroy", self.header.opcode);
self.reply::<ReplyEmpty>().error(EIO);
}
FUSE_INTERRUPT => {
let arg: &fuse_interrupt_in = data.fetch();
debug!("INTERRUPT({}) unique {}", self.header.unique, arg.unique);
self.reply::<ReplyEmpty>().error(ENOSYS);
},
FUSE_LOOKUP => {
let name = data.fetch_str();
debug!("LOOKUP({}) parent {:#018x}, name {:?}", self.header.unique, self.header.nodeid, name);
se.filesystem.lookup(self, self.header.nodeid, &name, self.reply());
},
FUSE_FORGET => {
let arg: &fuse_forget_in = data.fetch();
debug!("FORGET({}) ino {:#018x}, nlookup {}", self.header.unique, self.header.nodeid, arg.nlookup);
se.filesystem.forget(self, self.header.nodeid, arg.nlookup); },
FUSE_GETATTR => {
debug!("GETATTR({}) ino {:#018x}", self.header.unique, self.header.nodeid);
se.filesystem.getattr(self, self.header.nodeid, self.reply());
},
FUSE_SETATTR => {
let arg: &fuse_setattr_in = data.fetch();
debug!("SETATTR({}) ino {:#018x}, valid {:#x}", self.header.unique, self.header.nodeid, arg.valid);
let mode = match arg.valid & FATTR_MODE { 0 => None, _ => Some(arg.mode) };
let uid = match arg.valid & FATTR_UID { 0 => None, _ => Some(arg.uid) };
let gid = match arg.valid & FATTR_GID { 0 => None, _ => Some(arg.gid) };
let size = match arg.valid & FATTR_SIZE { 0 => None, _ => Some(arg.size) };
let atime = match arg.valid & FATTR_ATIME { 0 => None, _ => Some(Timespec::new(arg.atime, arg.atimensec)) };
let mtime = match arg.valid & FATTR_MTIME { 0 => None, _ => Some(Timespec::new(arg.mtime, arg.mtimensec)) };
let fh = match arg.valid & FATTR_FH { 0 => None, _ => Some(arg.fh) };
#[cfg(target_os = "macos")] #[inline]
fn get_macos_setattr (arg: &fuse_setattr_in) -> (Option<Timespec>, Option<Timespec>, Option<Timespec>, Option<u32>) {
let crtime = match arg.valid & FATTR_CRTIME { 0 => None, _ => Some(Timespec::new(arg.crtime, arg.crtimensec)) };
let chgtime = match arg.valid & FATTR_CHGTIME { 0 => None, _ => Some(Timespec::new(arg.chgtime, arg.chgtimensec)) };
let bkuptime = match arg.valid & FATTR_BKUPTIME { 0 => None, _ => Some(Timespec::new(arg.bkuptime, arg.bkuptimensec)) };
let flags = match arg.valid & FATTR_FLAGS { 0 => None, _ => Some(arg.flags) };
(crtime, chgtime, bkuptime, flags)
}
#[cfg(not(target_os = "macos"))] #[inline]
fn get_macos_setattr (_arg: &fuse_setattr_in) -> (Option<Timespec>, Option<Timespec>, Option<Timespec>, Option<u32>) {
(None, None, None, None)
}
let (crtime, chgtime, bkuptime, flags) = get_macos_setattr(arg);
se.filesystem.setattr(self, self.header.nodeid, mode, uid, gid, size, atime, mtime, fh, crtime, chgtime, bkuptime, flags, self.reply());
},
FUSE_READLINK => {
debug!("READLINK({}) ino {:#018x}", self.header.unique, self.header.nodeid);
se.filesystem.readlink(self, self.header.nodeid, self.reply());
},
FUSE_MKNOD => {
let arg: &fuse_mknod_in = data.fetch();
let name = data.fetch_str();
debug!("MKNOD({}) parent {:#018x}, name {:?}, mode {:#05o}, rdev {}", self.header.unique, self.header.nodeid, name, arg.mode, arg.rdev);
se.filesystem.mknod(self, self.header.nodeid, &name, arg.mode, arg.rdev, self.reply());
},
FUSE_MKDIR => {
let arg: &fuse_mkdir_in = data.fetch();
let name = data.fetch_str();
debug!("MKDIR({}) parent {:#018x}, name {:?}, mode {:#05o}", self.header.unique, self.header.nodeid, name, arg.mode);
se.filesystem.mkdir(self, self.header.nodeid, &name, arg.mode, self.reply());
},
FUSE_UNLINK => {
let name = data.fetch_str();
debug!("UNLINK({}) parent {:#018x}, name {:?}", self.header.unique, self.header.nodeid, name);
se.filesystem.unlink(self, self.header.nodeid, &name, self.reply());
},
FUSE_RMDIR => {
let name = data.fetch_str();
debug!("RMDIR({}) parent {:#018x}, name {:?}", self.header.unique, self.header.nodeid, name);
se.filesystem.rmdir(self, self.header.nodeid, &name, self.reply());
},
FUSE_SYMLINK => {
let name = data.fetch_str();
let link = data.fetch_path();
debug!("SYMLINK({}) parent {:#018x}, name {:?}, link {:?}", self.header.unique, self.header.nodeid, name, link);
se.filesystem.symlink(self, self.header.nodeid, &name, &link, self.reply());
},
FUSE_RENAME => {
let arg: &fuse_rename_in = data.fetch();
let name = data.fetch_str();
let newname = data.fetch_str();
debug!("RENAME({}) parent {:#018x}, name {:?}, newparent {:#018x}, newname {:?}", self.header.unique, self.header.nodeid, name, arg.newdir, newname);
se.filesystem.rename(self, self.header.nodeid, &name, arg.newdir, &newname, self.reply());
},
FUSE_LINK => {
let arg: &fuse_link_in = data.fetch();
let newname = data.fetch_str();
debug!("LINK({}) ino {:#018x}, newparent {:#018x}, newname {:?}", self.header.unique, arg.oldnodeid, self.header.nodeid, newname);
se.filesystem.link(self, arg.oldnodeid, self.header.nodeid, &newname, self.reply());
},
FUSE_OPEN => {
let arg: &fuse_open_in = data.fetch();
debug!("OPEN({}) ino {:#018x}, flags {:#x}", self.header.unique, self.header.nodeid, arg.flags);
se.filesystem.open(self, self.header.nodeid, arg.flags, self.reply());
},
FUSE_READ => {
let arg: &fuse_read_in = data.fetch();
debug!("READ({}) ino {:#018x}, fh {}, offset {}, size {}", self.header.unique, self.header.nodeid, arg.fh, arg.offset, arg.size);
se.filesystem.read(self, self.header.nodeid, arg.fh, arg.offset, arg.size, self.reply());
},
FUSE_WRITE => {
let arg: &fuse_write_in = data.fetch();
let data = data.fetch_data();
assert!(data.len() == arg.size as usize);
debug!("WRITE({}) ino {:#018x}, fh {}, offset {}, size {}, flags {:#x}", self.header.unique, self.header.nodeid, arg.fh, arg.offset, arg.size, arg.write_flags);
se.filesystem.write(self, self.header.nodeid, arg.fh, arg.offset, data, arg.write_flags, self.reply());
},
FUSE_FLUSH => {
let arg: &fuse_flush_in = data.fetch();
debug!("FLUSH({}) ino {:#018x}, fh {}, lock owner {}", self.header.unique, self.header.nodeid, arg.fh, arg.lock_owner);
se.filesystem.flush(self, self.header.nodeid, arg.fh, arg.lock_owner, self.reply());
},
FUSE_RELEASE => {
let arg: &fuse_release_in = data.fetch();
let flush = match arg.release_flags & FUSE_RELEASE_FLUSH { 0 => false, _ => true };
debug!("RELEASE({}) ino {:#018x}, fh {}, flags {:#x}, release flags {:#x}, lock owner {}", self.header.unique, self.header.nodeid, arg.fh, arg.flags, arg.release_flags, arg.lock_owner);
se.filesystem.release(self, self.header.nodeid, arg.fh, arg.flags, arg.lock_owner, flush, self.reply());
},
FUSE_FSYNC => {
let arg: &fuse_fsync_in = data.fetch();
let datasync = match arg.fsync_flags & 1 { 0 => false, _ => true };
debug!("FSYNC({}) ino {:#018x}, fh {}, flags {:#x}", self.header.unique, self.header.nodeid, arg.fh, arg.fsync_flags);
se.filesystem.fsync(self, self.header.nodeid, arg.fh, datasync, self.reply());
},
FUSE_OPENDIR => {
let arg: &fuse_open_in = data.fetch();
debug!("OPENDIR({}) ino {:#018x}, flags {:#x}", self.header.unique, self.header.nodeid, arg.flags);
se.filesystem.opendir(self, self.header.nodeid, arg.flags, self.reply());
},
FUSE_READDIR => {
let arg: &fuse_read_in = data.fetch();
debug!("READDIR({}) ino {:#018x}, fh {}, offset {}, size {}", self.header.unique, self.header.nodeid, arg.fh, arg.offset, arg.size);
se.filesystem.readdir(self, self.header.nodeid, arg.fh, arg.offset, ReplyDirectory::new(self.header.unique, self.ch, arg.size as usize));
},
FUSE_RELEASEDIR => {
let arg: &fuse_release_in = data.fetch();
debug!("RELEASEDIR({}) ino {:#018x}, fh {}, flags {:#x}, release flags {:#x}, lock owner {}", self.header.unique, self.header.nodeid, arg.fh, arg.flags, arg.release_flags, arg.lock_owner);
se.filesystem.releasedir(self, self.header.nodeid, arg.fh, arg.flags, self.reply());
},
FUSE_FSYNCDIR => {
let arg: &fuse_fsync_in = data.fetch();
let datasync = match arg.fsync_flags & 1 { 0 => false, _ => true };
debug!("FSYNCDIR({}) ino {:#018x}, fh {}, flags {:#x}", self.header.unique, self.header.nodeid, arg.fh, arg.fsync_flags);
se.filesystem.fsyncdir(self, self.header.nodeid, arg.fh, datasync, self.reply());
},
FUSE_STATFS => {
debug!("STATFS({}) ino {:#018x}", self.header.unique, self.header.nodeid);
se.filesystem.statfs(self, self.header.nodeid, self.reply());
},
FUSE_SETXATTR => {
let arg: &fuse_setxattr_in = data.fetch();
let name = data.fetch_str();
let value = data.fetch_data();
assert!(value.len() == arg.size as usize);
debug!("SETXATTR({}) ino {:#018x}, name {:?}, size {}, flags {:#x}", self.header.unique, self.header.nodeid, name, arg.size, arg.flags);
#[cfg(target_os = "macos")] #[inline]
fn get_position (arg: &fuse_setxattr_in) -> u32 { arg.position }
#[cfg(not(target_os = "macos"))] #[inline]
fn get_position (_arg: &fuse_setxattr_in) -> u32 { 0 }
se.filesystem.setxattr(self, self.header.nodeid, name, value, arg.flags, get_position(arg), self.reply());
},
FUSE_GETXATTR => {
let arg: &fuse_getxattr_in = data.fetch();
let name = data.fetch_str();
debug!("GETXATTR({}) ino {:#018x}, name {:?}, size {}", self.header.unique, self.header.nodeid, name, arg.size);
se.filesystem.getxattr(self, self.header.nodeid, name, arg.size, self.reply());
},
FUSE_LISTXATTR => {
let arg: &fuse_getxattr_in = data.fetch();
debug!("LISTXATTR({}) ino {:#018x}, size {}", self.header.unique, self.header.nodeid, arg.size);
se.filesystem.listxattr(self, self.header.nodeid, arg.size, self.reply());
},
FUSE_REMOVEXATTR => {
let name = data.fetch_str();
debug!("REMOVEXATTR({}) ino {:#018x}, name {:?}", self.header.unique, self.header.nodeid, name);
se.filesystem.removexattr(self, self.header.nodeid, name, self.reply());
},
FUSE_ACCESS => {
let arg: &fuse_access_in = data.fetch();
debug!("ACCESS({}) ino {:#018x}, mask {:#05o}", self.header.unique, self.header.nodeid, arg.mask);
se.filesystem.access(self, self.header.nodeid, arg.mask, self.reply());
},
FUSE_CREATE => {
let arg: &fuse_open_in = data.fetch();
let name = data.fetch_str();
debug!("CREATE({}) parent {:#018x}, name {:?}, mode {:#05o}, flags {:#x}", self.header.unique, self.header.nodeid, name, arg.mode, arg.flags);
se.filesystem.create(self, self.header.nodeid, &name, arg.mode, arg.flags, self.reply());
},
FUSE_GETLK => {
let arg: &fuse_lk_in = data.fetch();
debug!("GETLK({}) ino {:#018x}, fh {}, lock owner {}", self.header.unique, self.header.nodeid, arg.fh, arg.owner);
se.filesystem.getlk(self, self.header.nodeid, arg.fh, arg.owner, arg.lk.start, arg.lk.end, arg.lk.typ, arg.lk.pid, self.reply());
},
FUSE_SETLK | FUSE_SETLKW => {
let arg: &fuse_lk_in = data.fetch();
let sleep = match opcode { FUSE_SETLKW => true, _ => false };
debug!("SETLK({}) ino {:#018x}, fh {}, lock owner {}", self.header.unique, self.header.nodeid, arg.fh, arg.owner);
se.filesystem.setlk(self, self.header.nodeid, arg.fh, arg.owner, arg.lk.start, arg.lk.end, arg.lk.typ, arg.lk.pid, sleep, self.reply());
},
FUSE_BMAP => {
let arg: &fuse_bmap_in = data.fetch();
debug!("BMAP({}) ino {:#018x}, blocksize {}, ids {}", self.header.unique, self.header.nodeid, arg.blocksize, arg.block);
se.filesystem.bmap(self, self.header.nodeid, arg.blocksize, arg.block, self.reply());
},
#[cfg(target_os = "macos")]
FUSE_SETVOLNAME => { let name = data.fetch_str();
debug!("SETVOLNAME({}) name {:?}", self.header.unique, name);
se.filesystem.setvolname(self, name, self.reply());
},
#[cfg(target_os = "macos")]
FUSE_EXCHANGE => { let arg: &fuse_exchange_in = data.fetch();
let oldname = data.fetch_str();
let newname = data.fetch_str();
debug!("EXCHANGE({}) parent {:#018x}, name {:?}, newparent {:#018x}, newname {:?}, options {:#x}", self.header.unique, arg.olddir, oldname, arg.newdir, newname, arg.options);
se.filesystem.exchange(self, arg.olddir, &oldname, arg.newdir, &newname, arg.options, self.reply());
},
#[cfg(target_os = "macos")]
FUSE_GETXTIMES => { debug!("GETXTIMES({}) ino {:#018x}", self.header.unique, self.header.nodeid);
se.filesystem.getxtimes(self, self.header.nodeid, self.reply());
},
}
}
fn reply<T: Reply> (&self) -> T {
Reply::new(self.header.unique, self.ch)
}
#[inline] #[allow(dead_code)]
pub fn unique (&self) -> u64 {
self.header.unique
}
#[inline] #[allow(dead_code)]
pub fn uid (&self) -> u32 {
self.header.uid
}
#[inline] #[allow(dead_code)]
pub fn gid (&self) -> u32 {
self.header.gid
}
#[inline] #[allow(dead_code)]
pub fn pid (&self) -> u32 {
self.header.pid
}
}