#![allow(clippy::needless_return)]
use clap::{crate_version, App, Arg};
#[cfg(feature = "abi-7-26")]
use log::info;
use log::LevelFilter;
use log::{debug, warn};
use rofuse::consts::FOPEN_DIRECT_IO;
#[cfg(feature = "abi-7-26")]
use rofuse::consts::FUSE_HANDLE_KILLPRIV;
#[cfg(feature = "abi-7-31")]
use rofuse::consts::FUSE_WRITE_KILL_PRIV;
use rofuse::TimeOrNow::Now;
use rofuse::{
Filesystem, KernelConfig, MountOption, ReplyAttr, ReplyCreate, ReplyData, ReplyDirectory,
ReplyEmpty, ReplyEntry, ReplyOpen, ReplyStatfs, ReplyWrite, ReplyXattr, Request, TimeOrNow,
FUSE_ROOT_ID,
};
use serde::{Deserialize, Serialize};
use std::cmp::min;
use std::collections::BTreeMap;
use std::ffi::OsStr;
use std::fs::{File, OpenOptions};
use std::io::{BufRead, BufReader, Read, Seek, SeekFrom, Write};
use std::os::raw::c_int;
use std::os::unix::ffi::OsStrExt;
use std::os::unix::fs::FileExt;
#[cfg(target_os = "linux")]
use std::os::unix::io::IntoRawFd;
use std::path::{Path, PathBuf};
use std::sync::atomic::{AtomicU64, Ordering};
use std::time::{Duration, SystemTime, UNIX_EPOCH};
use std::{env, fs, io};
const BLOCK_SIZE: u64 = 512;
const MAX_NAME_LENGTH: u32 = 255;
const MAX_FILE_SIZE: u64 = 1024 * 1024 * 1024 * 1024;
const FILE_HANDLE_READ_BIT: u64 = 1 << 63;
const FILE_HANDLE_WRITE_BIT: u64 = 1 << 62;
const FMODE_EXEC: i32 = 0x20;
type Inode = u64;
type DirectoryDescriptor = BTreeMap<Vec<u8>, (Inode, FileKind)>;
#[derive(Serialize, Deserialize, Copy, Clone, PartialEq)]
enum FileKind {
File,
Directory,
Symlink,
}
impl From<FileKind> for rofuse::FileType {
fn from(kind: FileKind) -> Self {
match kind {
FileKind::File => rofuse::FileType::RegularFile,
FileKind::Directory => rofuse::FileType::Directory,
FileKind::Symlink => rofuse::FileType::Symlink,
}
}
}
#[derive(Debug)]
enum XattrNamespace {
Security,
System,
Trusted,
User,
}
fn parse_xattr_namespace(key: &[u8]) -> Result<XattrNamespace, c_int> {
let user = b"user.";
if key.len() < user.len() {
return Err(libc::ENOTSUP);
}
if key[..user.len()].eq(user) {
return Ok(XattrNamespace::User);
}
let system = b"system.";
if key.len() < system.len() {
return Err(libc::ENOTSUP);
}
if key[..system.len()].eq(system) {
return Ok(XattrNamespace::System);
}
let trusted = b"trusted.";
if key.len() < trusted.len() {
return Err(libc::ENOTSUP);
}
if key[..trusted.len()].eq(trusted) {
return Ok(XattrNamespace::Trusted);
}
let security = b"security";
if key.len() < security.len() {
return Err(libc::ENOTSUP);
}
if key[..security.len()].eq(security) {
return Ok(XattrNamespace::Security);
}
return Err(libc::ENOTSUP);
}
fn clear_suid_sgid(attr: &mut InodeAttributes) {
attr.mode &= !libc::S_ISUID as u16;
if attr.mode & libc::S_IXGRP as u16 != 0 {
attr.mode &= !libc::S_ISGID as u16;
}
}
fn creation_gid(parent: &InodeAttributes, gid: u32) -> u32 {
if parent.mode & libc::S_ISGID as u16 != 0 {
return parent.gid;
}
gid
}
fn xattr_access_check(
key: &[u8],
access_mask: i32,
inode_attrs: &InodeAttributes,
request: &Request<'_>,
) -> Result<(), c_int> {
match parse_xattr_namespace(key)? {
XattrNamespace::Security => {
if access_mask != libc::R_OK && request.uid() != 0 {
return Err(libc::EPERM);
}
}
XattrNamespace::Trusted => {
if request.uid() != 0 {
return Err(libc::EPERM);
}
}
XattrNamespace::System => {
if key.eq(b"system.posix_acl_access") {
if !check_access(
inode_attrs.uid,
inode_attrs.gid,
inode_attrs.mode,
request.uid(),
request.gid(),
access_mask,
) {
return Err(libc::EPERM);
}
} else if request.uid() != 0 {
return Err(libc::EPERM);
}
}
XattrNamespace::User => {
if !check_access(
inode_attrs.uid,
inode_attrs.gid,
inode_attrs.mode,
request.uid(),
request.gid(),
access_mask,
) {
return Err(libc::EPERM);
}
}
}
Ok(())
}
fn time_now() -> (i64, u32) {
time_from_system_time(&SystemTime::now())
}
fn system_time_from_time(secs: i64, nsecs: u32) -> SystemTime {
if secs >= 0 {
UNIX_EPOCH + Duration::new(secs as u64, nsecs)
} else {
UNIX_EPOCH - Duration::new((-secs) as u64, nsecs)
}
}
fn time_from_system_time(system_time: &SystemTime) -> (i64, u32) {
match system_time.duration_since(UNIX_EPOCH) {
Ok(duration) => (duration.as_secs() as i64, duration.subsec_nanos()),
Err(before_epoch_error) => (
-(before_epoch_error.duration().as_secs() as i64),
before_epoch_error.duration().subsec_nanos(),
),
}
}
#[derive(Serialize, Deserialize)]
struct InodeAttributes {
pub inode: Inode,
pub open_file_handles: u64, pub size: u64,
pub last_accessed: (i64, u32),
pub last_modified: (i64, u32),
pub last_metadata_changed: (i64, u32),
pub kind: FileKind,
pub mode: u16,
pub hardlinks: u32,
pub uid: u32,
pub gid: u32,
pub xattrs: BTreeMap<Vec<u8>, Vec<u8>>,
}
impl From<InodeAttributes> for rofuse::FileAttr {
fn from(attrs: InodeAttributes) -> Self {
rofuse::FileAttr {
ino: attrs.inode,
size: attrs.size,
blocks: (attrs.size + BLOCK_SIZE - 1) / BLOCK_SIZE,
atime: system_time_from_time(attrs.last_accessed.0, attrs.last_accessed.1),
mtime: system_time_from_time(attrs.last_modified.0, attrs.last_modified.1),
ctime: system_time_from_time(
attrs.last_metadata_changed.0,
attrs.last_metadata_changed.1,
),
crtime: SystemTime::UNIX_EPOCH,
kind: attrs.kind.into(),
perm: attrs.mode,
nlink: attrs.hardlinks,
uid: attrs.uid,
gid: attrs.gid,
rdev: 0,
blksize: BLOCK_SIZE as u32,
flags: 0,
}
}
}
struct SimpleFS {
data_dir: String,
next_file_handle: AtomicU64,
direct_io: bool,
suid_support: bool,
}
impl SimpleFS {
fn new(
data_dir: String,
direct_io: bool,
#[allow(unused_variables)] suid_support: bool,
) -> SimpleFS {
#[cfg(feature = "abi-7-26")]
{
SimpleFS {
data_dir,
next_file_handle: AtomicU64::new(1),
direct_io,
suid_support,
}
}
#[cfg(not(feature = "abi-7-26"))]
{
SimpleFS {
data_dir,
next_file_handle: AtomicU64::new(1),
direct_io,
suid_support: false,
}
}
}
fn creation_mode(&self, mode: u32) -> u16 {
if !self.suid_support {
(mode & !(libc::S_ISUID | libc::S_ISGID) as u32) as u16
} else {
mode as u16
}
}
fn allocate_next_inode(&self) -> Inode {
let path = Path::new(&self.data_dir).join("superblock");
let current_inode = if let Ok(file) = File::open(&path) {
bincode::deserialize_from(file).unwrap()
} else {
rofuse::FUSE_ROOT_ID
};
let file = OpenOptions::new()
.write(true)
.create(true)
.truncate(true)
.open(&path)
.unwrap();
bincode::serialize_into(file, &(current_inode + 1)).unwrap();
current_inode + 1
}
fn allocate_next_file_handle(&self, read: bool, write: bool) -> u64 {
let mut fh = self.next_file_handle.fetch_add(1, Ordering::SeqCst);
assert!(fh < FILE_HANDLE_WRITE_BIT && fh < FILE_HANDLE_READ_BIT);
if read {
fh |= FILE_HANDLE_READ_BIT;
}
if write {
fh |= FILE_HANDLE_WRITE_BIT;
}
fh
}
fn check_file_handle_read(&self, file_handle: u64) -> bool {
(file_handle & FILE_HANDLE_READ_BIT) != 0
}
fn check_file_handle_write(&self, file_handle: u64) -> bool {
(file_handle & FILE_HANDLE_WRITE_BIT) != 0
}
fn content_path(&self, inode: Inode) -> PathBuf {
Path::new(&self.data_dir)
.join("contents")
.join(inode.to_string())
}
fn get_directory_content(&self, inode: Inode) -> Result<DirectoryDescriptor, c_int> {
let path = Path::new(&self.data_dir)
.join("contents")
.join(inode.to_string());
if let Ok(file) = File::open(&path) {
Ok(bincode::deserialize_from(file).unwrap())
} else {
Err(libc::ENOENT)
}
}
fn write_directory_content(&self, inode: Inode, entries: DirectoryDescriptor) {
let path = Path::new(&self.data_dir)
.join("contents")
.join(inode.to_string());
let file = OpenOptions::new()
.write(true)
.create(true)
.truncate(true)
.open(&path)
.unwrap();
bincode::serialize_into(file, &entries).unwrap();
}
fn get_inode(&self, inode: Inode) -> Result<InodeAttributes, c_int> {
let path = Path::new(&self.data_dir)
.join("inodes")
.join(inode.to_string());
if let Ok(file) = File::open(&path) {
Ok(bincode::deserialize_from(file).unwrap())
} else {
Err(libc::ENOENT)
}
}
fn write_inode(&self, inode: &InodeAttributes) {
let path = Path::new(&self.data_dir)
.join("inodes")
.join(inode.inode.to_string());
let file = OpenOptions::new()
.write(true)
.create(true)
.truncate(true)
.open(&path)
.unwrap();
bincode::serialize_into(file, inode).unwrap();
}
fn gc_inode(&self, inode: &InodeAttributes) -> bool {
if inode.hardlinks == 0 && inode.open_file_handles == 0 {
let inode_path = Path::new(&self.data_dir)
.join("inodes")
.join(inode.inode.to_string());
fs::remove_file(inode_path).unwrap();
let content_path = Path::new(&self.data_dir)
.join("contents")
.join(inode.inode.to_string());
fs::remove_file(content_path).unwrap();
return true;
}
return false;
}
fn truncate(
&self,
inode: Inode,
new_length: u64,
uid: u32,
gid: u32,
) -> Result<InodeAttributes, c_int> {
if new_length > MAX_FILE_SIZE {
return Err(libc::EFBIG);
}
let mut attrs = self.get_inode(inode)?;
if !check_access(attrs.uid, attrs.gid, attrs.mode, uid, gid, libc::W_OK) {
return Err(libc::EACCES);
}
let path = self.content_path(inode);
let file = OpenOptions::new().write(true).open(&path).unwrap();
file.set_len(new_length).unwrap();
attrs.size = new_length;
attrs.last_metadata_changed = time_now();
attrs.last_modified = time_now();
clear_suid_sgid(&mut attrs);
self.write_inode(&attrs);
Ok(attrs)
}
fn lookup_name(&self, parent: u64, name: &OsStr) -> Result<InodeAttributes, c_int> {
let entries = self.get_directory_content(parent)?;
if let Some((inode, _)) = entries.get(name.as_bytes()) {
return self.get_inode(*inode);
} else {
return Err(libc::ENOENT);
}
}
fn insert_link(
&self,
req: &Request,
parent: u64,
name: &OsStr,
inode: u64,
kind: FileKind,
) -> Result<(), c_int> {
if self.lookup_name(parent, name).is_ok() {
return Err(libc::EEXIST);
}
let mut parent_attrs = self.get_inode(parent)?;
if !check_access(
parent_attrs.uid,
parent_attrs.gid,
parent_attrs.mode,
req.uid(),
req.gid(),
libc::W_OK,
) {
return Err(libc::EACCES);
}
parent_attrs.last_modified = time_now();
parent_attrs.last_metadata_changed = time_now();
self.write_inode(&parent_attrs);
let mut entries = self.get_directory_content(parent).unwrap();
entries.insert(name.as_bytes().to_vec(), (inode, kind));
self.write_directory_content(parent, entries);
Ok(())
}
}
impl Filesystem for SimpleFS {
fn init(
&mut self,
_req: &Request,
#[allow(unused_variables)] config: &mut KernelConfig,
) -> Result<(), c_int> {
#[cfg(feature = "abi-7-26")]
config.add_capabilities(FUSE_HANDLE_KILLPRIV).unwrap();
fs::create_dir_all(Path::new(&self.data_dir).join("inodes")).unwrap();
fs::create_dir_all(Path::new(&self.data_dir).join("contents")).unwrap();
if self.get_inode(FUSE_ROOT_ID).is_err() {
let root = InodeAttributes {
inode: FUSE_ROOT_ID,
open_file_handles: 0,
size: 0,
last_accessed: time_now(),
last_modified: time_now(),
last_metadata_changed: time_now(),
kind: FileKind::Directory,
mode: 0o777,
hardlinks: 2,
uid: 0,
gid: 0,
xattrs: Default::default(),
};
self.write_inode(&root);
let mut entries = BTreeMap::new();
entries.insert(b".".to_vec(), (FUSE_ROOT_ID, FileKind::Directory));
self.write_directory_content(FUSE_ROOT_ID, entries);
}
Ok(())
}
fn lookup(&mut self, req: &Request, parent: u64, name: &OsStr, reply: ReplyEntry) {
if name.len() > MAX_NAME_LENGTH as usize {
reply.error(libc::ENAMETOOLONG);
return;
}
let parent_attrs = self.get_inode(parent).unwrap();
if !check_access(
parent_attrs.uid,
parent_attrs.gid,
parent_attrs.mode,
req.uid(),
req.gid(),
libc::X_OK,
) {
reply.error(libc::EACCES);
return;
}
match self.lookup_name(parent, name) {
Ok(attrs) => reply.entry(&Duration::new(0, 0), &attrs.into(), 0),
Err(error_code) => reply.error(error_code),
}
}
fn forget(&mut self, _req: &Request, _ino: u64, _nlookup: u64) {}
fn getattr(&mut self, _req: &Request, inode: u64, reply: ReplyAttr) {
match self.get_inode(inode) {
Ok(attrs) => reply.attr(&Duration::new(0, 0), &attrs.into()),
Err(error_code) => reply.error(error_code),
}
}
fn setattr(
&mut self,
req: &Request,
inode: u64,
mode: Option<u32>,
uid: Option<u32>,
gid: Option<u32>,
size: Option<u64>,
atime: Option<TimeOrNow>,
mtime: Option<TimeOrNow>,
_ctime: Option<SystemTime>,
fh: Option<u64>,
_crtime: Option<SystemTime>,
_chgtime: Option<SystemTime>,
_bkuptime: Option<SystemTime>,
_flags: Option<u32>,
reply: ReplyAttr,
) {
let mut attrs = match self.get_inode(inode) {
Ok(attrs) => attrs,
Err(error_code) => {
reply.error(error_code);
return;
}
};
if let Some(mode) = mode {
debug!("chmod() called with {:?}, {:o}", inode, mode);
if req.uid() != 0 && req.uid() != attrs.uid {
reply.error(libc::EPERM);
return;
}
if req.uid() != 0
&& req.gid() != attrs.gid
&& !get_groups(req.pid()).contains(&attrs.gid)
{
attrs.mode = (mode & !libc::S_ISGID as u32) as u16;
} else {
attrs.mode = mode as u16;
}
attrs.last_metadata_changed = time_now();
self.write_inode(&attrs);
reply.attr(&Duration::new(0, 0), &attrs.into());
return;
}
if uid.is_some() || gid.is_some() {
debug!("chown() called with {:?} {:?} {:?}", inode, uid, gid);
if let Some(gid) = gid {
if req.uid() != 0 && !get_groups(req.pid()).contains(&gid) {
reply.error(libc::EPERM);
return;
}
}
if let Some(uid) = uid {
if req.uid() != 0
&& !(uid == attrs.uid && req.uid() == attrs.uid)
{
reply.error(libc::EPERM);
return;
}
}
if gid.is_some() && req.uid() != 0 && req.uid() != attrs.uid {
reply.error(libc::EPERM);
return;
}
if attrs.mode & (libc::S_IXUSR | libc::S_IXGRP | libc::S_IXOTH) as u16 != 0 {
clear_suid_sgid(&mut attrs);
}
if let Some(uid) = uid {
attrs.uid = uid;
attrs.mode &= !libc::S_ISUID as u16;
}
if let Some(gid) = gid {
attrs.gid = gid;
if req.uid() != 0 {
attrs.mode &= !libc::S_ISGID as u16;
}
}
attrs.last_metadata_changed = time_now();
self.write_inode(&attrs);
reply.attr(&Duration::new(0, 0), &attrs.into());
return;
}
if let Some(size) = size {
debug!("truncate() called with {:?} {:?}", inode, size);
if let Some(handle) = fh {
if self.check_file_handle_write(handle) {
if let Err(error_code) = self.truncate(inode, size, 0, 0) {
reply.error(error_code);
return;
}
} else {
reply.error(libc::EACCES);
return;
}
} else if let Err(error_code) = self.truncate(inode, size, req.uid(), req.gid()) {
reply.error(error_code);
return;
}
}
let now = time_now();
if let Some(atime) = atime {
debug!("utimens() called with {:?}, atime={:?}", inode, atime);
if attrs.uid != req.uid() && req.uid() != 0 && atime != Now {
reply.error(libc::EPERM);
return;
}
if attrs.uid != req.uid()
&& !check_access(
attrs.uid,
attrs.gid,
attrs.mode,
req.uid(),
req.gid(),
libc::W_OK,
)
{
reply.error(libc::EACCES);
return;
}
attrs.last_accessed = match atime {
TimeOrNow::SpecificTime(time) => time_from_system_time(&time),
Now => now,
};
attrs.last_metadata_changed = now;
self.write_inode(&attrs);
}
if let Some(mtime) = mtime {
debug!("utimens() called with {:?}, mtime={:?}", inode, mtime);
if attrs.uid != req.uid() && req.uid() != 0 && mtime != Now {
reply.error(libc::EPERM);
return;
}
if attrs.uid != req.uid()
&& !check_access(
attrs.uid,
attrs.gid,
attrs.mode,
req.uid(),
req.gid(),
libc::W_OK,
)
{
reply.error(libc::EACCES);
return;
}
attrs.last_modified = match mtime {
TimeOrNow::SpecificTime(time) => time_from_system_time(&time),
Now => now,
};
attrs.last_metadata_changed = now;
self.write_inode(&attrs);
}
let attrs = self.get_inode(inode).unwrap();
reply.attr(&Duration::new(0, 0), &attrs.into());
return;
}
fn readlink(&mut self, _req: &Request, inode: u64, reply: ReplyData) {
debug!("readlink() called on {:?}", inode);
let path = self.content_path(inode);
if let Ok(mut file) = File::open(&path) {
let file_size = file.metadata().unwrap().len();
let mut buffer = vec![0; file_size as usize];
file.read_exact(&mut buffer).unwrap();
reply.data(&buffer);
} else {
reply.error(libc::ENOENT);
}
}
fn mknod(
&mut self,
req: &Request,
parent: u64,
name: &OsStr,
mut mode: u32,
_umask: u32,
_rdev: u32,
reply: ReplyEntry,
) {
let file_type = mode & libc::S_IFMT as u32;
if file_type != libc::S_IFREG as u32
&& file_type != libc::S_IFLNK as u32
&& file_type != libc::S_IFDIR as u32
{
warn!("mknod() implementation is incomplete. Only supports regular files, symlinks, and directories. Got {:o}", mode);
reply.error(libc::ENOSYS);
return;
}
if self.lookup_name(parent, name).is_ok() {
reply.error(libc::EEXIST);
return;
}
let mut parent_attrs = match self.get_inode(parent) {
Ok(attrs) => attrs,
Err(error_code) => {
reply.error(error_code);
return;
}
};
if !check_access(
parent_attrs.uid,
parent_attrs.gid,
parent_attrs.mode,
req.uid(),
req.gid(),
libc::W_OK,
) {
reply.error(libc::EACCES);
return;
}
parent_attrs.last_modified = time_now();
parent_attrs.last_metadata_changed = time_now();
self.write_inode(&parent_attrs);
if req.uid() != 0 {
mode &= !(libc::S_ISUID | libc::S_ISGID) as u32;
}
let inode = self.allocate_next_inode();
let attrs = InodeAttributes {
inode,
open_file_handles: 0,
size: 0,
last_accessed: time_now(),
last_modified: time_now(),
last_metadata_changed: time_now(),
kind: as_file_kind(mode),
mode: self.creation_mode(mode),
hardlinks: 1,
uid: req.uid(),
gid: creation_gid(&parent_attrs, req.gid()),
xattrs: Default::default(),
};
self.write_inode(&attrs);
File::create(self.content_path(inode)).unwrap();
if as_file_kind(mode) == FileKind::Directory {
let mut entries = BTreeMap::new();
entries.insert(b".".to_vec(), (inode, FileKind::Directory));
entries.insert(b"..".to_vec(), (parent, FileKind::Directory));
self.write_directory_content(inode, entries);
}
let mut entries = self.get_directory_content(parent).unwrap();
entries.insert(name.as_bytes().to_vec(), (inode, attrs.kind));
self.write_directory_content(parent, entries);
reply.entry(&Duration::new(0, 0), &attrs.into(), 0);
}
fn mkdir(
&mut self,
req: &Request,
parent: u64,
name: &OsStr,
mut mode: u32,
_umask: u32,
reply: ReplyEntry,
) {
debug!("mkdir() called with {:?} {:?} {:o}", parent, name, mode);
if self.lookup_name(parent, name).is_ok() {
reply.error(libc::EEXIST);
return;
}
let mut parent_attrs = match self.get_inode(parent) {
Ok(attrs) => attrs,
Err(error_code) => {
reply.error(error_code);
return;
}
};
if !check_access(
parent_attrs.uid,
parent_attrs.gid,
parent_attrs.mode,
req.uid(),
req.gid(),
libc::W_OK,
) {
reply.error(libc::EACCES);
return;
}
parent_attrs.last_modified = time_now();
parent_attrs.last_metadata_changed = time_now();
self.write_inode(&parent_attrs);
if req.uid() != 0 {
mode &= !(libc::S_ISUID | libc::S_ISGID) as u32;
}
if parent_attrs.mode & libc::S_ISGID as u16 != 0 {
mode |= libc::S_ISGID as u32;
}
let inode = self.allocate_next_inode();
let attrs = InodeAttributes {
inode,
open_file_handles: 0,
size: BLOCK_SIZE,
last_accessed: time_now(),
last_modified: time_now(),
last_metadata_changed: time_now(),
kind: FileKind::Directory,
mode: self.creation_mode(mode),
hardlinks: 2, uid: req.uid(),
gid: creation_gid(&parent_attrs, req.gid()),
xattrs: Default::default(),
};
self.write_inode(&attrs);
let mut entries = BTreeMap::new();
entries.insert(b".".to_vec(), (inode, FileKind::Directory));
entries.insert(b"..".to_vec(), (parent, FileKind::Directory));
self.write_directory_content(inode, entries);
let mut entries = self.get_directory_content(parent).unwrap();
entries.insert(name.as_bytes().to_vec(), (inode, FileKind::Directory));
self.write_directory_content(parent, entries);
reply.entry(&Duration::new(0, 0), &attrs.into(), 0);
}
fn unlink(&mut self, req: &Request, parent: u64, name: &OsStr, reply: ReplyEmpty) {
debug!("unlink() called with {:?} {:?}", parent, name);
let mut attrs = match self.lookup_name(parent, name) {
Ok(attrs) => attrs,
Err(error_code) => {
reply.error(error_code);
return;
}
};
let mut parent_attrs = match self.get_inode(parent) {
Ok(attrs) => attrs,
Err(error_code) => {
reply.error(error_code);
return;
}
};
if !check_access(
parent_attrs.uid,
parent_attrs.gid,
parent_attrs.mode,
req.uid(),
req.gid(),
libc::W_OK,
) {
reply.error(libc::EACCES);
return;
}
let uid = req.uid();
if parent_attrs.mode & libc::S_ISVTX as u16 != 0
&& uid != 0
&& uid != parent_attrs.uid
&& uid != attrs.uid
{
reply.error(libc::EACCES);
return;
}
parent_attrs.last_metadata_changed = time_now();
parent_attrs.last_modified = time_now();
self.write_inode(&parent_attrs);
attrs.hardlinks -= 1;
attrs.last_metadata_changed = time_now();
self.write_inode(&attrs);
self.gc_inode(&attrs);
let mut entries = self.get_directory_content(parent).unwrap();
entries.remove(name.as_bytes());
self.write_directory_content(parent, entries);
reply.ok();
}
fn rmdir(&mut self, req: &Request, parent: u64, name: &OsStr, reply: ReplyEmpty) {
debug!("rmdir() called with {:?} {:?}", parent, name);
let mut attrs = match self.lookup_name(parent, name) {
Ok(attrs) => attrs,
Err(error_code) => {
reply.error(error_code);
return;
}
};
let mut parent_attrs = match self.get_inode(parent) {
Ok(attrs) => attrs,
Err(error_code) => {
reply.error(error_code);
return;
}
};
if self.get_directory_content(attrs.inode).unwrap().len() > 2 {
reply.error(libc::ENOTEMPTY);
return;
}
if !check_access(
parent_attrs.uid,
parent_attrs.gid,
parent_attrs.mode,
req.uid(),
req.gid(),
libc::W_OK,
) {
reply.error(libc::EACCES);
return;
}
if parent_attrs.mode & libc::S_ISVTX as u16 != 0
&& req.uid() != 0
&& req.uid() != parent_attrs.uid
&& req.uid() != attrs.uid
{
reply.error(libc::EACCES);
return;
}
parent_attrs.last_metadata_changed = time_now();
parent_attrs.last_modified = time_now();
self.write_inode(&parent_attrs);
attrs.hardlinks = 0;
attrs.last_metadata_changed = time_now();
self.write_inode(&attrs);
self.gc_inode(&attrs);
let mut entries = self.get_directory_content(parent).unwrap();
entries.remove(name.as_bytes());
self.write_directory_content(parent, entries);
reply.ok();
}
fn symlink(
&mut self,
req: &Request,
parent: u64,
name: &OsStr,
link: &Path,
reply: ReplyEntry,
) {
debug!("symlink() called with {:?} {:?} {:?}", parent, name, link);
let mut parent_attrs = match self.get_inode(parent) {
Ok(attrs) => attrs,
Err(error_code) => {
reply.error(error_code);
return;
}
};
if !check_access(
parent_attrs.uid,
parent_attrs.gid,
parent_attrs.mode,
req.uid(),
req.gid(),
libc::W_OK,
) {
reply.error(libc::EACCES);
return;
}
parent_attrs.last_modified = time_now();
parent_attrs.last_metadata_changed = time_now();
self.write_inode(&parent_attrs);
let inode = self.allocate_next_inode();
let attrs = InodeAttributes {
inode,
open_file_handles: 0,
size: link.as_os_str().as_bytes().len() as u64,
last_accessed: time_now(),
last_modified: time_now(),
last_metadata_changed: time_now(),
kind: FileKind::Symlink,
mode: 0o777,
hardlinks: 1,
uid: req.uid(),
gid: creation_gid(&parent_attrs, req.gid()),
xattrs: Default::default(),
};
if let Err(error_code) = self.insert_link(req, parent, name, inode, FileKind::Symlink) {
reply.error(error_code);
return;
}
self.write_inode(&attrs);
let path = self.content_path(inode);
let mut file = OpenOptions::new()
.write(true)
.create(true)
.truncate(true)
.open(&path)
.unwrap();
file.write_all(link.as_os_str().as_bytes()).unwrap();
reply.entry(&Duration::new(0, 0), &attrs.into(), 0);
}
fn rename(
&mut self,
req: &Request,
parent: u64,
name: &OsStr,
new_parent: u64,
new_name: &OsStr,
flags: u32,
reply: ReplyEmpty,
) {
let mut inode_attrs = match self.lookup_name(parent, name) {
Ok(attrs) => attrs,
Err(error_code) => {
reply.error(error_code);
return;
}
};
let mut parent_attrs = match self.get_inode(parent) {
Ok(attrs) => attrs,
Err(error_code) => {
reply.error(error_code);
return;
}
};
if !check_access(
parent_attrs.uid,
parent_attrs.gid,
parent_attrs.mode,
req.uid(),
req.gid(),
libc::W_OK,
) {
reply.error(libc::EACCES);
return;
}
if parent_attrs.mode & libc::S_ISVTX as u16 != 0
&& req.uid() != 0
&& req.uid() != parent_attrs.uid
&& req.uid() != inode_attrs.uid
{
reply.error(libc::EACCES);
return;
}
let mut new_parent_attrs = match self.get_inode(new_parent) {
Ok(attrs) => attrs,
Err(error_code) => {
reply.error(error_code);
return;
}
};
if !check_access(
new_parent_attrs.uid,
new_parent_attrs.gid,
new_parent_attrs.mode,
req.uid(),
req.gid(),
libc::W_OK,
) {
reply.error(libc::EACCES);
return;
}
if new_parent_attrs.mode & libc::S_ISVTX as u16 != 0 {
if let Ok(existing_attrs) = self.lookup_name(new_parent, new_name) {
if req.uid() != 0
&& req.uid() != new_parent_attrs.uid
&& req.uid() != existing_attrs.uid
{
reply.error(libc::EACCES);
return;
}
}
}
#[cfg(target_os = "linux")]
if flags & libc::RENAME_EXCHANGE as u32 != 0 {
let mut new_inode_attrs = match self.lookup_name(new_parent, new_name) {
Ok(attrs) => attrs,
Err(error_code) => {
reply.error(error_code);
return;
}
};
let mut entries = self.get_directory_content(new_parent).unwrap();
entries.insert(
new_name.as_bytes().to_vec(),
(inode_attrs.inode, inode_attrs.kind),
);
self.write_directory_content(new_parent, entries);
let mut entries = self.get_directory_content(parent).unwrap();
entries.insert(
name.as_bytes().to_vec(),
(new_inode_attrs.inode, new_inode_attrs.kind),
);
self.write_directory_content(parent, entries);
parent_attrs.last_metadata_changed = time_now();
parent_attrs.last_modified = time_now();
self.write_inode(&parent_attrs);
new_parent_attrs.last_metadata_changed = time_now();
new_parent_attrs.last_modified = time_now();
self.write_inode(&new_parent_attrs);
inode_attrs.last_metadata_changed = time_now();
self.write_inode(&inode_attrs);
new_inode_attrs.last_metadata_changed = time_now();
self.write_inode(&new_inode_attrs);
if inode_attrs.kind == FileKind::Directory {
let mut entries = self.get_directory_content(inode_attrs.inode).unwrap();
entries.insert(b"..".to_vec(), (new_parent, FileKind::Directory));
self.write_directory_content(inode_attrs.inode, entries);
}
if new_inode_attrs.kind == FileKind::Directory {
let mut entries = self.get_directory_content(new_inode_attrs.inode).unwrap();
entries.insert(b"..".to_vec(), (parent, FileKind::Directory));
self.write_directory_content(new_inode_attrs.inode, entries);
}
reply.ok();
return;
}
if let Ok(new_name_attrs) = self.lookup_name(new_parent, new_name) {
if new_name_attrs.kind == FileKind::Directory
&& self
.get_directory_content(new_name_attrs.inode)
.unwrap()
.len()
> 2
{
reply.error(libc::ENOTEMPTY);
return;
}
}
if inode_attrs.kind == FileKind::Directory
&& parent != new_parent
&& !check_access(
inode_attrs.uid,
inode_attrs.gid,
inode_attrs.mode,
req.uid(),
req.gid(),
libc::W_OK,
)
{
reply.error(libc::EACCES);
return;
}
if let Ok(mut existing_inode_attrs) = self.lookup_name(new_parent, new_name) {
let mut entries = self.get_directory_content(new_parent).unwrap();
entries.remove(new_name.as_bytes());
self.write_directory_content(new_parent, entries);
if existing_inode_attrs.kind == FileKind::Directory {
existing_inode_attrs.hardlinks = 0;
} else {
existing_inode_attrs.hardlinks -= 1;
}
existing_inode_attrs.last_metadata_changed = time_now();
self.write_inode(&existing_inode_attrs);
self.gc_inode(&existing_inode_attrs);
}
let mut entries = self.get_directory_content(parent).unwrap();
entries.remove(name.as_bytes());
self.write_directory_content(parent, entries);
let mut entries = self.get_directory_content(new_parent).unwrap();
entries.insert(
new_name.as_bytes().to_vec(),
(inode_attrs.inode, inode_attrs.kind),
);
self.write_directory_content(new_parent, entries);
parent_attrs.last_metadata_changed = time_now();
parent_attrs.last_modified = time_now();
self.write_inode(&parent_attrs);
new_parent_attrs.last_metadata_changed = time_now();
new_parent_attrs.last_modified = time_now();
self.write_inode(&new_parent_attrs);
inode_attrs.last_metadata_changed = time_now();
self.write_inode(&inode_attrs);
if inode_attrs.kind == FileKind::Directory {
let mut entries = self.get_directory_content(inode_attrs.inode).unwrap();
entries.insert(b"..".to_vec(), (new_parent, FileKind::Directory));
self.write_directory_content(inode_attrs.inode, entries);
}
reply.ok();
}
fn link(
&mut self,
req: &Request,
inode: u64,
new_parent: u64,
new_name: &OsStr,
reply: ReplyEntry,
) {
debug!(
"link() called for {}, {}, {:?}",
inode, new_parent, new_name
);
let mut attrs = match self.get_inode(inode) {
Ok(attrs) => attrs,
Err(error_code) => {
reply.error(error_code);
return;
}
};
if let Err(error_code) = self.insert_link(&req, new_parent, new_name, inode, attrs.kind) {
reply.error(error_code);
} else {
attrs.hardlinks += 1;
attrs.last_metadata_changed = time_now();
self.write_inode(&attrs);
reply.entry(&Duration::new(0, 0), &attrs.into(), 0);
}
}
fn open(&mut self, req: &Request, inode: u64, flags: i32, reply: ReplyOpen) {
debug!("open() called for {:?}", inode);
let (access_mask, read, write) = match flags & libc::O_ACCMODE {
libc::O_RDONLY => {
if flags & libc::O_TRUNC != 0 {
reply.error(libc::EACCES);
return;
}
if flags & FMODE_EXEC != 0 {
(libc::X_OK, true, false)
} else {
(libc::R_OK, true, false)
}
}
libc::O_WRONLY => (libc::W_OK, false, true),
libc::O_RDWR => (libc::R_OK | libc::W_OK, true, true),
_ => {
reply.error(libc::EINVAL);
return;
}
};
match self.get_inode(inode) {
Ok(mut attr) => {
if check_access(
attr.uid,
attr.gid,
attr.mode,
req.uid(),
req.gid(),
access_mask,
) {
attr.open_file_handles += 1;
self.write_inode(&attr);
let open_flags = if self.direct_io { FOPEN_DIRECT_IO } else { 0 };
reply.opened(self.allocate_next_file_handle(read, write), open_flags);
return;
} else {
reply.error(libc::EACCES);
return;
}
}
Err(error_code) => reply.error(error_code),
}
}
fn read(
&mut self,
_req: &Request,
inode: u64,
fh: u64,
offset: i64,
size: u32,
_flags: i32,
_lock_owner: Option<u64>,
reply: ReplyData,
) {
debug!(
"read() called on {:?} offset={:?} size={:?}",
inode, offset, size
);
assert!(offset >= 0);
if !self.check_file_handle_read(fh) {
reply.error(libc::EACCES);
return;
}
let path = self.content_path(inode);
if let Ok(file) = File::open(&path) {
let file_size = file.metadata().unwrap().len();
let read_size = min(size, file_size.saturating_sub(offset as u64) as u32);
let mut buffer = vec![0; read_size as usize];
file.read_exact_at(&mut buffer, offset as u64).unwrap();
reply.data(&buffer);
} else {
reply.error(libc::ENOENT);
}
}
fn write(
&mut self,
_req: &Request,
inode: u64,
fh: u64,
offset: i64,
data: &[u8],
_write_flags: u32,
#[allow(unused_variables)] flags: i32,
_lock_owner: Option<u64>,
reply: ReplyWrite,
) {
debug!("write() called with {:?} size={:?}", inode, data.len());
assert!(offset >= 0);
if !self.check_file_handle_write(fh) {
reply.error(libc::EACCES);
return;
}
let path = self.content_path(inode);
if let Ok(mut file) = OpenOptions::new().write(true).open(&path) {
file.seek(SeekFrom::Start(offset as u64)).unwrap();
file.write_all(data).unwrap();
let mut attrs = self.get_inode(inode).unwrap();
attrs.last_metadata_changed = time_now();
attrs.last_modified = time_now();
if data.len() + offset as usize > attrs.size as usize {
attrs.size = (data.len() + offset as usize) as u64;
}
clear_suid_sgid(&mut attrs);
self.write_inode(&attrs);
reply.written(data.len() as u32);
} else {
reply.error(libc::EBADF);
}
}
fn release(
&mut self,
_req: &Request<'_>,
inode: u64,
_fh: u64,
_flags: i32,
_lock_owner: Option<u64>,
_flush: bool,
reply: ReplyEmpty,
) {
if let Ok(mut attrs) = self.get_inode(inode) {
attrs.open_file_handles -= 1;
}
reply.ok();
}
fn opendir(&mut self, req: &Request, inode: u64, flags: i32, reply: ReplyOpen) {
debug!("opendir() called on {:?}", inode);
let (access_mask, read, write) = match flags & libc::O_ACCMODE {
libc::O_RDONLY => {
if flags & libc::O_TRUNC != 0 {
reply.error(libc::EACCES);
return;
}
(libc::R_OK, true, false)
}
libc::O_WRONLY => (libc::W_OK, false, true),
libc::O_RDWR => (libc::R_OK | libc::W_OK, true, true),
_ => {
reply.error(libc::EINVAL);
return;
}
};
match self.get_inode(inode) {
Ok(mut attr) => {
if check_access(
attr.uid,
attr.gid,
attr.mode,
req.uid(),
req.gid(),
access_mask,
) {
attr.open_file_handles += 1;
self.write_inode(&attr);
let open_flags = if self.direct_io { FOPEN_DIRECT_IO } else { 0 };
reply.opened(self.allocate_next_file_handle(read, write), open_flags);
return;
} else {
reply.error(libc::EACCES);
return;
}
}
Err(error_code) => reply.error(error_code),
}
}
fn readdir(
&mut self,
_req: &Request,
inode: u64,
_fh: u64,
offset: i64,
mut reply: ReplyDirectory,
) {
debug!("readdir() called with {:?}", inode);
assert!(offset >= 0);
let entries = match self.get_directory_content(inode) {
Ok(entries) => entries,
Err(error_code) => {
reply.error(error_code);
return;
}
};
for (index, entry) in entries.iter().skip(offset as usize).enumerate() {
let (name, (inode, file_type)) = entry;
let buffer_full: bool = reply.add(
*inode,
offset + index as i64 + 1,
(*file_type).into(),
OsStr::from_bytes(name),
);
if buffer_full {
break;
}
}
reply.ok();
}
fn releasedir(
&mut self,
_req: &Request<'_>,
inode: u64,
_fh: u64,
_flags: i32,
reply: ReplyEmpty,
) {
if let Ok(mut attrs) = self.get_inode(inode) {
attrs.open_file_handles -= 1;
}
reply.ok();
}
fn statfs(&mut self, _req: &Request, _ino: u64, reply: ReplyStatfs) {
warn!("statfs() implementation is a stub");
reply.statfs(
10_000,
10_000,
10_000,
1,
10_000,
BLOCK_SIZE as u32,
MAX_NAME_LENGTH,
BLOCK_SIZE as u32,
);
}
fn setxattr(
&mut self,
request: &Request<'_>,
inode: u64,
key: &OsStr,
value: &[u8],
_flags: i32,
_position: u32,
reply: ReplyEmpty,
) {
if let Ok(mut attrs) = self.get_inode(inode) {
if let Err(error) = xattr_access_check(key.as_bytes(), libc::W_OK, &attrs, request) {
reply.error(error);
return;
}
attrs.xattrs.insert(key.as_bytes().to_vec(), value.to_vec());
attrs.last_metadata_changed = time_now();
self.write_inode(&attrs);
reply.ok();
} else {
reply.error(libc::EBADF);
}
}
fn getxattr(
&mut self,
request: &Request<'_>,
inode: u64,
key: &OsStr,
size: u32,
reply: ReplyXattr,
) {
if let Ok(attrs) = self.get_inode(inode) {
if let Err(error) = xattr_access_check(key.as_bytes(), libc::R_OK, &attrs, request) {
reply.error(error);
return;
}
if let Some(data) = attrs.xattrs.get(key.as_bytes()) {
if size == 0 {
reply.size(data.len() as u32);
} else if data.len() <= size as usize {
reply.data(&data);
} else {
reply.error(libc::ERANGE);
}
} else {
#[cfg(target_os = "linux")]
reply.error(libc::ENODATA);
#[cfg(not(target_os = "linux"))]
reply.error(libc::ENOATTR);
}
} else {
reply.error(libc::EBADF);
}
}
fn listxattr(&mut self, _req: &Request<'_>, inode: u64, size: u32, reply: ReplyXattr) {
if let Ok(attrs) = self.get_inode(inode) {
let mut bytes = vec![];
for key in attrs.xattrs.keys() {
bytes.extend(key);
bytes.push(0);
}
if size == 0 {
reply.size(bytes.len() as u32);
} else if bytes.len() <= size as usize {
reply.data(&bytes);
} else {
reply.error(libc::ERANGE);
}
} else {
reply.error(libc::EBADF);
}
}
fn removexattr(&mut self, request: &Request<'_>, inode: u64, key: &OsStr, reply: ReplyEmpty) {
if let Ok(mut attrs) = self.get_inode(inode) {
if let Err(error) = xattr_access_check(key.as_bytes(), libc::W_OK, &attrs, request) {
reply.error(error);
return;
}
if attrs.xattrs.remove(key.as_bytes()).is_none() {
#[cfg(target_os = "linux")]
reply.error(libc::ENODATA);
#[cfg(not(target_os = "linux"))]
reply.error(libc::ENOATTR);
return;
}
attrs.last_metadata_changed = time_now();
self.write_inode(&attrs);
reply.ok();
} else {
reply.error(libc::EBADF);
}
}
fn access(&mut self, req: &Request, inode: u64, mask: i32, reply: ReplyEmpty) {
debug!("access() called with {:?} {:?}", inode, mask);
match self.get_inode(inode) {
Ok(attr) => {
if check_access(attr.uid, attr.gid, attr.mode, req.uid(), req.gid(), mask) {
reply.ok();
} else {
reply.error(libc::EACCES);
}
}
Err(error_code) => reply.error(error_code),
}
}
fn create(
&mut self,
req: &Request,
parent: u64,
name: &OsStr,
mut mode: u32,
_umask: u32,
flags: i32,
reply: ReplyCreate,
) {
debug!("create() called with {:?} {:?}", parent, name);
if self.lookup_name(parent, name).is_ok() {
reply.error(libc::EEXIST);
return;
}
let (read, write) = match flags & libc::O_ACCMODE {
libc::O_RDONLY => (true, false),
libc::O_WRONLY => (false, true),
libc::O_RDWR => (true, true),
_ => {
reply.error(libc::EINVAL);
return;
}
};
let mut parent_attrs = match self.get_inode(parent) {
Ok(attrs) => attrs,
Err(error_code) => {
reply.error(error_code);
return;
}
};
if !check_access(
parent_attrs.uid,
parent_attrs.gid,
parent_attrs.mode,
req.uid(),
req.gid(),
libc::W_OK,
) {
reply.error(libc::EACCES);
return;
}
parent_attrs.last_modified = time_now();
parent_attrs.last_metadata_changed = time_now();
self.write_inode(&parent_attrs);
if req.uid() != 0 {
mode &= !(libc::S_ISUID | libc::S_ISGID) as u32;
}
let inode = self.allocate_next_inode();
let attrs = InodeAttributes {
inode,
open_file_handles: 1,
size: 0,
last_accessed: time_now(),
last_modified: time_now(),
last_metadata_changed: time_now(),
kind: as_file_kind(mode),
mode: self.creation_mode(mode),
hardlinks: 1,
uid: req.uid(),
gid: creation_gid(&parent_attrs, req.gid()),
xattrs: Default::default(),
};
self.write_inode(&attrs);
File::create(self.content_path(inode)).unwrap();
if as_file_kind(mode) == FileKind::Directory {
let mut entries = BTreeMap::new();
entries.insert(b".".to_vec(), (inode, FileKind::Directory));
entries.insert(b"..".to_vec(), (parent, FileKind::Directory));
self.write_directory_content(inode, entries);
}
let mut entries = self.get_directory_content(parent).unwrap();
entries.insert(name.as_bytes().to_vec(), (inode, attrs.kind));
self.write_directory_content(parent, entries);
reply.created(
&Duration::new(0, 0),
&attrs.into(),
0,
self.allocate_next_file_handle(read, write),
0,
);
}
#[cfg(target_os = "linux")]
fn fallocate(
&mut self,
_req: &Request<'_>,
inode: u64,
_fh: u64,
offset: i64,
length: i64,
mode: i32,
reply: ReplyEmpty,
) {
let path = self.content_path(inode);
if let Ok(file) = OpenOptions::new().write(true).open(&path) {
unsafe {
libc::fallocate64(file.into_raw_fd(), mode, offset, length);
}
if mode & libc::FALLOC_FL_KEEP_SIZE == 0 {
let mut attrs = self.get_inode(inode).unwrap();
attrs.last_metadata_changed = time_now();
attrs.last_modified = time_now();
if (offset + length) as u64 > attrs.size {
attrs.size = (offset + length) as u64;
}
self.write_inode(&attrs);
}
reply.ok();
} else {
reply.error(libc::ENOENT);
}
}
fn copy_file_range(
&mut self,
_req: &Request<'_>,
src_inode: u64,
src_fh: u64,
src_offset: i64,
dest_inode: u64,
dest_fh: u64,
dest_offset: i64,
size: u64,
_flags: u32,
reply: ReplyWrite,
) {
debug!(
"copy_file_range() called with src ({}, {}, {}) dest ({}, {}, {}) size={}",
src_fh, src_inode, src_offset, dest_fh, dest_inode, dest_offset, size
);
if !self.check_file_handle_read(src_fh) {
reply.error(libc::EACCES);
return;
}
if !self.check_file_handle_write(dest_fh) {
reply.error(libc::EACCES);
return;
}
let src_path = self.content_path(src_inode);
if let Ok(file) = File::open(&src_path) {
let file_size = file.metadata().unwrap().len();
let read_size = min(size, file_size.saturating_sub(src_offset as u64));
let mut data = vec![0; read_size as usize];
file.read_exact_at(&mut data, src_offset as u64).unwrap();
let dest_path = self.content_path(dest_inode);
if let Ok(mut file) = OpenOptions::new().write(true).open(&dest_path) {
file.seek(SeekFrom::Start(dest_offset as u64)).unwrap();
file.write_all(&data).unwrap();
let mut attrs = self.get_inode(dest_inode).unwrap();
attrs.last_metadata_changed = time_now();
attrs.last_modified = time_now();
if data.len() + dest_offset as usize > attrs.size as usize {
attrs.size = (data.len() + dest_offset as usize) as u64;
}
self.write_inode(&attrs);
reply.written(data.len() as u32);
} else {
reply.error(libc::EBADF);
}
} else {
reply.error(libc::ENOENT);
}
}
}
pub fn check_access(
file_uid: u32,
file_gid: u32,
file_mode: u16,
uid: u32,
gid: u32,
mut access_mask: i32,
) -> bool {
if access_mask == libc::F_OK {
return true;
}
let file_mode = i32::from(file_mode);
if uid == 0 {
access_mask &= libc::X_OK;
access_mask -= access_mask & (file_mode >> 6);
access_mask -= access_mask & (file_mode >> 3);
access_mask -= access_mask & file_mode;
return access_mask == 0;
}
if uid == file_uid {
access_mask -= access_mask & (file_mode >> 6);
} else if gid == file_gid {
access_mask -= access_mask & (file_mode >> 3);
} else {
access_mask -= access_mask & file_mode;
}
return access_mask == 0;
}
fn as_file_kind(mut mode: u32) -> FileKind {
mode &= libc::S_IFMT as u32;
if mode == libc::S_IFREG as u32 {
return FileKind::File;
} else if mode == libc::S_IFLNK as u32 {
return FileKind::Symlink;
} else if mode == libc::S_IFDIR as u32 {
return FileKind::Directory;
} else {
unimplemented!("{}", mode);
}
}
fn get_groups(pid: u32) -> Vec<u32> {
let path = format!("/proc/{}/task/{}/status", pid, pid);
let file = File::open(path).unwrap();
for line in BufReader::new(file).lines() {
let line = line.unwrap();
if line.starts_with("Groups:") {
return line["Groups: ".len()..]
.split(' ')
.filter(|x| !x.trim().is_empty())
.map(|x| x.parse::<u32>().unwrap())
.collect();
}
}
vec![]
}
fn fuse_allow_other_enabled() -> io::Result<bool> {
let file = File::open("/etc/fuse.conf")?;
for line in BufReader::new(file).lines() {
if line?.trim_start().starts_with("user_allow_other") {
return Ok(true);
}
}
Ok(false)
}
fn main() {
let matches = App::new("Fuser")
.version(crate_version!())
.author("Christopher Berner")
.arg(
Arg::with_name("data-dir")
.long("data-dir")
.value_name("DIR")
.default_value("/tmp/fuser")
.help("Set local directory used to store data")
.takes_value(true),
)
.arg(
Arg::with_name("mount-point")
.long("mount-point")
.value_name("MOUNT_POINT")
.default_value("")
.help("Act as a client, and mount FUSE at given path")
.takes_value(true),
)
.arg(
Arg::with_name("direct-io")
.long("direct-io")
.requires("mount-point")
.help("Mount FUSE with direct IO"),
)
.arg(
Arg::with_name("fsck")
.long("fsck")
.help("Run a filesystem check"),
)
.arg(
Arg::with_name("suid")
.long("suid")
.help("Enable setuid support when run as root"),
)
.arg(
Arg::with_name("v")
.short("v")
.multiple(true)
.help("Sets the level of verbosity"),
)
.get_matches();
let verbosity: u64 = matches.occurrences_of("v");
let log_level = match verbosity {
0 => LevelFilter::Error,
1 => LevelFilter::Warn,
2 => LevelFilter::Info,
3 => LevelFilter::Debug,
_ => LevelFilter::Trace,
};
env_logger::builder()
.format_timestamp_nanos()
.filter_level(log_level)
.init();
let mut options = vec![MountOption::FSName("fuser".to_string())];
#[cfg(feature = "abi-7-26")]
{
if matches.is_present("suid") {
info!("setuid bit support enabled");
options.push(MountOption::Suid);
} else {
options.push(MountOption::AutoUnmount);
}
}
#[cfg(not(feature = "abi-7-26"))]
{
options.push(MountOption::AutoUnmount);
}
if let Ok(enabled) = fuse_allow_other_enabled() {
if enabled {
options.push(MountOption::AllowOther);
}
} else {
eprintln!("Unable to read /etc/fuse.conf");
}
let data_dir: String = matches.value_of("data-dir").unwrap_or_default().to_string();
let mountpoint: String = matches
.value_of("mount-point")
.unwrap_or_default()
.to_string();
rofuse::mount2(
SimpleFS::new(
data_dir,
matches.is_present("direct-io"),
matches.is_present("suid"),
),
mountpoint,
&options,
)
.unwrap();
}