use std::cmp::min; use std::ffi::OsStr; use std::fs::File; use std::io::{BufRead, BufReader}; use std::os::raw::c_int; use std::time::{Duration, SystemTime}; use fuser::{FileAttr, Filesystem, FileType, KernelConfig, ReplyAttr, ReplyCreate, ReplyData, ReplyDirectory, ReplyEmpty, ReplyEntry, ReplyOpen, ReplyStatfs, ReplyWrite, Request, TimeOrNow}; use fuser::consts::FOPEN_DIRECT_IO; use fuser::TimeOrNow::Now; use libc::{EBADF, EIO, ENOENT, ENOTDIR, ENOTEMPTY}; use log::{debug, warn}; use crate::encryptedfs::{EncryptedFs, EncryptionType, FsError, FsResult}; const BLOCK_SIZE: u64 = 512; const FMODE_EXEC: i32 = 0x20; const MAX_NAME_LENGTH: u32 = 255; pub struct EncryptedFsFuse { fs: EncryptedFs, direct_io: bool, suid_support: bool, dir_handle: u64, } impl EncryptedFsFuse { pub fn new(data_dir: &str, password: &str, cipher: EncryptionType, derive_key_hash_rounds: u32, direct_io: bool, _suid_support: bool) -> FsResult { #[cfg(feature = "abi-7-26")] { Ok(EncryptedFsFuse { fs: EncryptedFs::new(data_dir, password, cipher, derive_key_hash_rounds)?, direct_io, suid_support: _suid_support, current_file_handle: 0, }) } #[cfg(not(feature = "abi-7-26"))] { Ok(EncryptedFsFuse { fs: EncryptedFs::new(data_dir, password, cipher, derive_key_hash_rounds)?, direct_io, suid_support: false, dir_handle: 0, }) } } fn creation_mode(&self, mode: u32) -> u16 { if !self.suid_support { (mode & !(libc::S_ISUID | libc::S_ISGID)) as u16 } else { mode as u16 } } fn create_nod(&mut self, parent: u64, mut mode: u32, req: &Request, name: &OsStr, read: bool, write: bool) -> Result<(u64, FileAttr), c_int> { match self.fs.get_inode(parent) { Err(_) => { Err(ENOENT) } Ok(parent_attr) => { if !check_access( parent_attr.uid, parent_attr.gid, parent_attr.perm, req.uid(), req.gid(), libc::W_OK, ) { return Err(libc::EACCES); } if req.uid() != 0 { mode &= !(libc::S_ISUID | libc::S_ISGID); } let kind = as_file_kind(mode); let mut attr = if kind == FileType::Directory { dir_attr() } else { file_attr(0) }; attr.perm = self.creation_mode(mode); attr.uid = req.uid(); attr.gid = creation_gid(&parent_attr, req.gid()); match self.fs.create_nod(parent, name.to_str().unwrap(), attr, read, write) { Ok(attr) => { Ok(attr) } Err(err) => { match err { FsError::AlreadyExists => { Err(libc::EEXIST) } _ => { return Err(ENOENT); } } } } } } } } impl Filesystem for EncryptedFsFuse { 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(); Ok(()) } fn lookup(&mut self, req: &Request, parent: u64, name: &OsStr, reply: ReplyEntry) { debug!("lookup {}, {}", parent, name.to_str().unwrap()); if name.len() > MAX_NAME_LENGTH as usize { reply.error(libc::ENAMETOOLONG); return; } match self.fs.get_inode(parent) { Err(err) => { debug!("not found {} {:?} {}", parent, name, err); reply.error(ENOENT); } Ok(parent_attr) => { if !check_access( parent_attr.uid, parent_attr.gid, parent_attr.perm, req.uid(), req.gid(), libc::X_OK, ) { reply.error(libc::EACCES); return; } match self.fs.find_by_name(parent, name.to_str().unwrap()) { Ok(Some(attr)) => { if attr.kind == FileType::Directory { debug!("dir {}", attr.ino); reply.entry(&Duration::new(0, 0), &&attr, 0); } else { debug!("file {}", attr.ino); reply.entry(&Duration::new(0, 0), &&attr, 0); } } _ => { debug!("not found"); reply.error(ENOENT); } } } } } fn forget(&mut self, _req: &Request<'_>, _ino: u64, _nlookup: u64) { debug!("forget() called with {:?} {:?}", _ino, _nlookup); } fn getattr(&mut self, _req: &Request, ino: u64, reply: ReplyAttr) { debug!("getattr {}", ino); match self.fs.get_inode(ino) { Err(err) => { debug!("not found {}", err); reply.error(ENOENT) } Ok(attr) => { if attr.kind == FileType::Directory { debug!("dir {}", ino); reply.attr(&Duration::new(0, 0), &attr); } else { debug!("file {}", ino); reply.attr(&Duration::new(0, 0), &attr); } } } } fn setattr( &mut self, req: &Request, inode: u64, mode: Option, uid: Option, gid: Option, size: Option, atime: Option, mtime: Option, _ctime: Option, fh: Option, _crtime: Option, _chgtime: Option, _bkuptime: Option, _flags: Option, reply: ReplyAttr, ) { debug!("setattr() called with {:?} {:?} {:?} {:?} {:?} {:?} {:?} {:?}", inode, mode, uid, gid, size, atime, mtime, fh); let mut attr = if let Ok(attr) = self.fs.get_inode(inode) { attr } else { reply.error(ENOENT); return; }; if let Some(mode) = mode { debug!("chmod() called with {:?}, {:o}", inode, mode); if req.uid() != 0 && req.uid() != attr.uid { reply.error(libc::EPERM); return; } if req.uid() != 0 && req.gid() != attr.gid && !get_groups(req.pid()).contains(&attr.gid) { // If SGID is set and the file belongs to a group that the caller is not part of // then the SGID bit is suppose to be cleared during chmod attr.perm = (mode & !libc::S_ISGID as u32) as u16; } else { attr.perm = mode as u16; } attr.ctime = SystemTime::now(); if let Err(err) = self.fs.update_inode(inode, attr.perm, attr.atime, attr.mtime, attr.ctime, attr.crtime, attr.uid, attr.gid, attr.size, attr.nlink, attr.flags) { debug!("chmod error {}", err); reply.error(ENOENT); return; } reply.attr(&Duration::new(0, 0), &attr); return; } if uid.is_some() || gid.is_some() { debug!("chown() called with {:?} {:?} {:?}", inode, uid, gid); if let Some(gid) = gid { // Non-root users can only change gid to a group they're in if req.uid() != 0 && !get_groups(req.pid()).contains(&gid) { reply.error(libc::EPERM); return; } } if let Some(uid) = uid { if req.uid() != 0 // but no-op changes by the owner are not an error && !(uid == attr.uid && req.uid() == attr.uid) { reply.error(libc::EPERM); return; } } // Only owner may change the group if gid.is_some() && req.uid() != 0 && req.uid() != attr.uid { reply.error(libc::EPERM); return; } if attr.perm & (libc::S_IXUSR | libc::S_IXGRP | libc::S_IXOTH) as u16 != 0 { // SUID & SGID are suppose to be cleared when chown'ing an executable file clear_suid_sgid(&mut attr); } if let Some(uid) = uid { attr.uid = uid; // Clear SETUID on owner change attr.perm &= !libc::S_ISUID as u16; } if let Some(gid) = gid { attr.gid = gid; // Clear SETGID unless user is root if req.uid() != 0 { attr.perm &= !libc::S_ISGID as u16; } } attr.ctime = SystemTime::now(); if let Err(err) = self.fs.update_inode(inode, attr.perm, attr.atime, attr.mtime, attr.ctime, attr.crtime, attr.uid, attr.gid, attr.size, attr.nlink, attr.flags) { debug!("chmod error {}", err); reply.error(ENOENT); return; } reply.attr(&Duration::new(0, 0), &attr); return; } if let Some(size) = size { debug!("truncate() called with {:?} {:?}", inode, size); if let Err(err) = self.fs.truncate(inode, size) { debug!("truncate error {}", err); reply.error(EBADF); return; } attr.size = size; // Clear SETUID & SETGID on truncate clear_suid_sgid(&mut attr); } if let Some(atime) = atime { debug!("utimens() called with {:?}, atime={:?}", inode, atime); if attr.uid != req.uid() && req.uid() != 0 && atime != Now { reply.error(libc::EPERM); return; } if attr.uid != req.uid() && !check_access( attr.uid, attr.gid, attr.perm, req.uid(), req.gid(), libc::W_OK, ) { reply.error(libc::EACCES); return; } attr.atime = match atime { TimeOrNow::SpecificTime(time) => time, Now => SystemTime::now(), }; attr.ctime = SystemTime::now(); } if let Some(mtime) = mtime { debug!("utimens() called with {:?}, mtime={:?}", inode, mtime); if attr.uid != req.uid() && req.uid() != 0 && mtime != Now { reply.error(libc::EPERM); return; } if attr.uid != req.uid() && !check_access( attr.uid, attr.gid, attr.perm, req.uid(), req.gid(), libc::W_OK, ) { reply.error(libc::EACCES); return; } attr.mtime = match mtime { TimeOrNow::SpecificTime(time) => time, Now => SystemTime::now(), }; attr.ctime = SystemTime::now(); } if let Err(err) = self.fs.update_inode(inode, attr.perm, attr.atime, attr.mtime, attr.ctime, attr.crtime, attr.uid, attr.gid, attr.size, attr.nlink, attr.flags) { debug!("setattr error {}", err); reply.error(ENOENT); return; } reply.attr(&Duration::new(0, 0), &attr); return; } fn mknod( &mut self, req: &Request, parent: u64, name: &OsStr, mode: u32, _umask: u32, _rdev: u32, reply: ReplyEntry, ) { debug!("mknod() called with {:?} {:?} {:o}", parent, name, mode); 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 { // TODO warn!("mknod() implementation is incomplete. Only supports regular files and directories. Got {:o}", mode); reply.error(libc::ENOSYS); return; } match self.create_nod(parent, mode, req, name, false, false) { Ok((_, attr)) => { // TODO: implement flags reply.entry(&Duration::new(0, 0), &attr, 0); } Err(err) => reply.error(err) } } 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.fs.exists_by_name(parent, name.to_str().unwrap()) { reply.error(libc::EEXIST); return; } match self.fs.get_inode(parent) { Err(_) => { reply.error(ENOENT); return; } Ok(parent_attr) => { if !check_access( parent_attr.uid, parent_attr.gid, parent_attr.perm, req.uid(), req.gid(), libc::W_OK, ) { reply.error(libc::EACCES); return; } let mut attr = dir_attr(); attr.size = BLOCK_SIZE; attr.atime = SystemTime::now(); attr.mtime = SystemTime::now(); attr.ctime = SystemTime::now(); if req.uid() != 0 { mode &= !(libc::S_ISUID | libc::S_ISGID); } if parent_attr.perm & libc::S_ISGID as u16 != 0 { mode |= libc::S_ISGID as u32; } attr.perm = self.creation_mode(mode); attr.uid = req.uid(); attr.gid = creation_gid(&parent_attr, req.gid()); match self.fs.create_nod(parent, name.to_str().unwrap(), attr, false, false) { Err(err) => { debug!("mkdir error {}", err); reply.error(ENOENT); return; } Ok((_, attr)) => reply.entry(&Duration::new(0, 0), &attr, 0) } } } } fn unlink(&mut self, req: &Request, parent: u64, name: &OsStr, reply: ReplyEmpty) { debug!("unlink() called with {:?} {:?}", parent, name); match self.fs.get_inode(parent) { Err(_) => { reply.error(ENOENT) } Ok(parent_attr) => { if parent_attr.kind != FileType::Directory { reply.error(ENOENT); return; } match self.fs.find_by_name(parent, name.to_str().unwrap()) { Ok(Some(attr)) => { let uid = req.uid(); // "Sticky bit" handling if parent_attr.perm & libc::S_ISVTX as u16 != 0 && uid != 0 && uid != parent_attr.uid && uid != attr.uid { reply.error(libc::EACCES); return; } if let Err(err) = self.fs.remove_file(parent, name.to_str().unwrap()) { debug!("unlink error {}", err); reply.error(ENOENT); return; } reply.ok(); } _ => { reply.error(ENOENT); return; } } } } } fn rmdir(&mut self, req: &Request, parent: u64, name: &OsStr, reply: ReplyEmpty) { debug!("rmdir() called with {:?} {:?}", parent, name); match self.fs.get_inode(parent) { Ok(parent_attr) => { if !check_access( parent_attr.uid, parent_attr.gid, parent_attr.perm, req.uid(), req.gid(), libc::W_OK, ) { reply.error(libc::EACCES); return; } match self.fs.find_by_name(parent, name.to_str().unwrap()) { Ok(Some(attr)) => { if attr.kind != FileType::Directory { reply.error(ENOTDIR); return; } // "Sticky bit" handling if parent_attr.perm & libc::S_ISVTX as u16 != 0 && req.uid() != 0 && req.uid() != parent_attr.uid && req.uid() != attr.uid { reply.error(libc::EACCES); return; } if let Err(err) = self.fs.remove_dir(parent, name.to_str().unwrap()) { debug!("rmdir error {}", err); reply.error(ENOENT); return; } reply.ok(); } _ => reply.error(ENOENT) } } _ => reply.error(ENOENT) } } fn rename( &mut self, req: &Request, parent: u64, name: &OsStr, new_parent: u64, new_name: &OsStr, _flags: u32, reply: ReplyEmpty, ) { let attr = match self.fs.find_by_name(parent, name.to_str().unwrap()) { Ok(Some(attr)) => attr, _ => { reply.error(ENOENT); return; } }; let parent_attr = match self.fs.get_inode(parent) { Ok(parent_attr) => parent_attr, _ => { reply.error(ENOENT); return; } }; if !check_access( parent_attr.uid, parent_attr.gid, parent_attr.perm, req.uid(), req.gid(), libc::W_OK) { reply.error(libc::EACCES); return; } // "Sticky bit" handling if parent_attr.perm & libc::S_ISVTX as u16 != 0 && req.uid() != 0 && req.uid() != parent_attr.uid && req.uid() != attr.uid { reply.error(libc::EACCES); return; } let new_parent_attr = match self.fs.get_inode(new_parent) { Ok(new_parent_attr) => new_parent_attr, _ => { reply.error(ENOENT); return; } }; if !check_access( new_parent_attr.uid, new_parent_attr.gid, new_parent_attr.perm, req.uid(), req.gid(), libc::W_OK, ) { reply.error(libc::EACCES); return; } // "Sticky bit" handling in new_parent if new_parent_attr.perm & libc::S_ISVTX as u16 != 0 { if let Ok(Some(new_attrs)) = self.fs.find_by_name(new_parent, new_name.to_str().unwrap()) { if req.uid() != 0 && req.uid() != new_parent_attr.uid && req.uid() != new_attrs.uid { reply.error(libc::EACCES); return; } } } // Only move an existing directory to a new parent, if we have write access to it, // because that will change the ".." link in it if attr.kind == FileType::Directory && parent != new_parent && !check_access( attr.uid, attr.gid, attr.perm, req.uid(), req.gid(), libc::W_OK, ) { reply.error(libc::EACCES); return; } match self.fs.rename(parent, name.to_str().unwrap(), new_parent, new_name.to_str().unwrap()) { Ok(_) => reply.ok(), Err(FsError::NotEmpty) => { reply.error(ENOTEMPTY); return; } _ => { reply.error(ENOENT); return; } } } 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 => { // Behavior is undefined, but most filesystems return EACCES if flags & libc::O_TRUNC != 0 { reply.error(libc::EACCES); return; } if flags & FMODE_EXEC != 0 { // Open is from internal exec syscall (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), // Exactly one access mode flag must be specified _ => { reply.error(libc::EINVAL); return; } }; match self.fs.get_inode(inode) { Ok(attr) => { if check_access(attr.uid, attr.gid, attr.perm, req.uid(), req.gid(), access_mask) { let open_flags = if self.direct_io { FOPEN_DIRECT_IO } else { 0 }; match self.fs.open(inode, read, write) { Err(_) => { reply.error(EBADF); return; } Ok(handle) => { debug!("opened handle {}", handle); reply.opened(handle, open_flags); } } } else { reply.error(libc::EACCES); } } _ => reply.error(ENOENT) } } fn read( &mut self, _req: &Request, ino: u64, fh: u64, offset: i64, size: u32, _flags: i32, _lock: Option, reply: ReplyData, ) { debug!("read {} {} {}", ino, offset, size); match self.fs.get_inode(ino) { Err(err) => { debug!("not found {}", err); reply.error(ENOENT) } Ok(attr) => { if attr.kind == FileType::Directory { reply.error(ENOENT); return; } let read_size = min(size, attr.size as u32 - offset as u32); debug!("read size={}", read_size); let mut buffer = vec![0; read_size as usize]; match self.fs.read(ino, offset as u64, &mut buffer, fh) { Err(err) => { debug!("read error {}", err); reply.error(EIO); return; } Ok(len) => { reply.data(&buffer[..len]); } } } } } 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, reply: ReplyWrite, ) { debug!("write() called with {:?} offfset {:?} size={:?}", inode, offset, data.len()); match self.fs.write_all(inode, offset as u64, data, fh) { Err(err) => { debug!("write error {}", err); reply.error(EIO); return; } Ok(_) => { let mut attr = self.fs.get_inode(inode).unwrap(); // XXX: In theory we should only need to do this when WRITE_KILL_PRIV is set for 7.31+ // However, xfstests fail in that case clear_suid_sgid(&mut attr); if let Err(err) = self.fs.update_inode(inode, attr.perm, attr.atime, attr.mtime, attr.ctime, attr.crtime, attr.uid, attr.gid, attr.size, attr.nlink, attr.flags) { debug!("write error {}", err); reply.error(ENOENT); return; } reply.written(data.len() as u32); } } } fn flush(&mut self, _req: &Request<'_>, ino: u64, fh: u64, lock_owner: u64, reply: ReplyEmpty) { debug!("flush() called with {:?} {:?} {:?}", ino, fh, lock_owner); if let Err(err) = self.fs.flush(fh) { debug!("flush error {}", err); reply.error(EBADF); return; } reply.ok(); } fn release(&mut self, _req: &Request<'_>, ino: u64, fh: u64, _flags: i32, _lock_owner: Option, flush: bool, reply: ReplyEmpty) { debug!("release() called with {:?} {:?} {:?}", ino, fh, _lock_owner); if flush { if let Err(err) = self.fs.flush(fh) { debug!("flush error {}", err); reply.error(EIO); return; } } if let Err(err) = self.fs.release_handle(fh) { debug!("release error {}", err); reply.error(EIO); return; } 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 => { // Behavior is undefined, but most filesystems return EACCES 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), // Exactly one access mode flag must be specified _ => { reply.error(libc::EINVAL); return; } }; match self.fs.get_inode(inode) { Ok(attr) => { if check_access( attr.uid, attr.gid, attr.perm, req.uid(), req.gid(), access_mask, ) { let open_flags = if self.direct_io { FOPEN_DIRECT_IO } else { 0 }; self.dir_handle += 1; reply.opened(self.dir_handle, open_flags); } else { reply.error(libc::EACCES); } } _ => reply.error(ENOENT) } } fn readdir( &mut self, _req: &Request, ino: u64, fh: u64, offset: i64, mut reply: ReplyDirectory, ) { debug!("readdir {} {} {}", ino, fh, offset); match self.fs.read_dir(ino) { Ok(iter) => { for (i, entry) in iter.enumerate().skip(offset as usize) { if entry.is_err() { reply.error(EIO); return; } let entry = entry.unwrap(); if reply.add(entry.ino, (i + 1) as i64, entry.kind, entry.name) { break; } } } Err(FsError::InvalidInodeType) => { reply.error(ENOTDIR); return; } _ => { reply.error(ENOENT); return; } } reply.ok(); } fn releasedir( &mut self, _req: &Request<'_>, inode: u64, _fh: u64, _flags: i32, reply: ReplyEmpty, ) { debug!("releasedir() called with {:?} {:?}", inode, _fh); reply.ok(); } fn statfs(&mut self, _req: &Request, _ino: u64, reply: ReplyStatfs) { warn!("statfs() implementation is a stub"); // TODO: real implementation of this reply.statfs( 10_000, 10_000, 10_000, 1, 10_000, BLOCK_SIZE as u32, MAX_NAME_LENGTH, BLOCK_SIZE as u32, ); } fn access(&mut self, req: &Request, inode: u64, mask: i32, reply: ReplyEmpty) { debug!("access() called with {:?} {:?}", inode, mask); match self.fs.get_inode(inode) { Ok(attr) => { if check_access(attr.uid, attr.gid, attr.perm, req.uid(), req.gid(), mask) { reply.ok(); } else { reply.error(libc::EACCES); } } _ => reply.error(ENOENT), } } fn create( &mut self, req: &Request, parent: u64, name: &OsStr, mode: u32, _umask: u32, flags: i32, reply: ReplyCreate, ) { debug!("create() called with {:?} {:?}", parent, name); let (read, write) = match flags & libc::O_ACCMODE { libc::O_RDONLY => (true, false), libc::O_WRONLY => (false, true), libc::O_RDWR => (true, true), // Exactly one access mode flag must be specified _ => { reply.error(libc::EINVAL); return; } }; match self.create_nod(parent, mode, req, name, read, write) { Ok((handle, attr)) => { debug!("created handle {}", handle); // TODO: implement flags reply.created( &Duration::new(0, 0), &attr, 0, handle, 0, ); } Err(err) => reply.error(err) } } 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 ); match self.fs.copy_file_range(src_inode, src_offset as u64, dest_inode, dest_offset as u64, size as usize, src_fh, dest_fh) { Err(err) => { debug!("copy_file_range error {}", err); reply.error(EBADF); return; } Ok(len) => { reply.written(len as u32); } } } } fn dir_attr() -> FileAttr { let mut f = FileAttr { ino: 0, size: BLOCK_SIZE, blocks: 0, atime: SystemTime::now(), mtime: SystemTime::now(), ctime: SystemTime::now(), crtime: SystemTime::now(), kind: FileType::Directory, perm: 0o777, nlink: 2, uid: 0, gid: 0, rdev: 0, flags: 0, blksize: BLOCK_SIZE as u32, }; f.blocks = (f.size + BLOCK_SIZE - 1) / BLOCK_SIZE; f } fn file_attr(size: u64) -> FileAttr { let mut f = FileAttr { ino: 0, size, blocks: (size + BLOCK_SIZE - 1) / BLOCK_SIZE, atime: SystemTime::now(), mtime: SystemTime::now(), ctime: SystemTime::now(), crtime: SystemTime::now(), kind: FileType::RegularFile, perm: 0o644, nlink: 1, uid: 0, gid: 0, rdev: 0, flags: 0, blksize: 512, }; f.blocks = (f.size + BLOCK_SIZE - 1) / BLOCK_SIZE; f } fn creation_gid(parent: &FileAttr, gid: u32) -> u32 { if parent.perm & libc::S_ISGID as u16 != 0 { return parent.gid; } gid } fn check_access( file_uid: u32, file_gid: u32, file_mode: u16, uid: u32, gid: u32, mut access_mask: i32, ) -> bool { // F_OK tests for existence of file if access_mask == libc::F_OK { return true; } let file_mode = i32::from(file_mode); // root is allowed to read & write anything if uid == 0 { // root only allowed to exec if one of the X bits is set 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 get_groups(pid: u32) -> Vec { #[cfg(not(target_os = "macos"))] { let path = format!("/proc/{pid}/task/{pid}/status"); 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::().unwrap()) .collect(); } } } vec![] } fn as_file_kind(mut mode: u32) -> FileType { mode &= libc::S_IFMT as u32; if mode == libc::S_IFREG as u32 { return FileType::RegularFile; } else if mode == libc::S_IFLNK as u32 { return FileType::Symlink; } else if mode == libc::S_IFDIR as u32 { return FileType::Directory; } else { unimplemented!("{}", mode); } } fn clear_suid_sgid(attr: &mut FileAttr) { attr.perm &= !libc::S_ISUID as u16; // SGID is only suppose to be cleared if XGRP is set if attr.perm & libc::S_IXGRP as u16 != 0 { attr.perm &= !libc::S_ISGID as u16; } }