Skip to content

feat: implement cwd, umask, access, faccessat, ftruncate, fchmod, truncate #1826

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 5 commits into from
Aug 18, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions src/errno.rs
Original file line number Diff line number Diff line change
Expand Up @@ -751,6 +751,12 @@ impl ToErrno for isize {
}
}

impl ToErrno for Errno {
fn to_errno(&self) -> Option<i32> {
Some(i32::from(*self))
}
}

impl ToErrno for u8 {}
impl ToErrno for u16 {}
impl ToErrno for u32 {}
Expand Down
67 changes: 67 additions & 0 deletions src/fd/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,52 @@ bitflags! {
}
}

bitflags! {
/// Options for checking file permissions or existence
#[derive(Debug, Copy, Clone, Default, Eq, PartialEq)]
pub struct AccessOption: i32 {
/// Test for read permission
const R_OK = 4;
/// Test for write permission
const W_OK = 2;
/// Test for execution permission
const X_OK = 1;
/// Test for existence
const F_OK = 0;
}
}

impl AccessOption {
/// Verifies if the current access options are all valid for the provided file access permissions
pub fn can_access(&self, access_permissions: AccessPermission) -> bool {
if self.contains(AccessOption::R_OK)
&& !access_permissions.contains(AccessPermission::S_IRUSR)
&& !access_permissions.contains(AccessPermission::S_IRGRP)
&& !access_permissions.contains(AccessPermission::S_IROTH)
{
return false;
}

if self.contains(AccessOption::W_OK)
&& !access_permissions.contains(AccessPermission::S_IWUSR)
&& !access_permissions.contains(AccessPermission::S_IWGRP)
&& !access_permissions.contains(AccessPermission::S_IWOTH)
{
return false;
}

if self.contains(AccessOption::X_OK)
&& !access_permissions.contains(AccessPermission::S_IXUSR)
&& !access_permissions.contains(AccessPermission::S_IXGRP)
&& !access_permissions.contains(AccessPermission::S_IXOTH)
{
return false;
}

true
}
}

bitflags! {
/// File status flags.
#[derive(Debug, Copy, Clone, Default)]
Expand Down Expand Up @@ -268,6 +314,16 @@ pub(crate) trait ObjectInterface: Sync + Send + core::fmt::Debug {
Err(Errno::Nosys)
}

/// Truncates the file
async fn truncate(&self, _size: usize) -> io::Result<()> {
Err(Errno::Nosys)
}

/// Changes access permissions to the file
async fn chmod(&self, _access_permission: AccessPermission) -> io::Result<()> {
Err(Errno::Nosys)
}

/// `isatty` returns `true` for a terminal device
async fn isatty(&self) -> io::Result<bool> {
Ok(false)
Expand All @@ -290,6 +346,12 @@ pub(crate) fn lseek(fd: FileDescriptor, offset: isize, whence: SeekWhence) -> io
block_on(obj.lseek(offset, whence), None)
}

pub(crate) fn chmod(fd: FileDescriptor, mode: AccessPermission) -> io::Result<()> {
let obj = get_object(fd)?;

block_on(obj.chmod(mode), None)
}

pub(crate) fn write(fd: FileDescriptor, buf: &[u8]) -> io::Result<usize> {
let obj = get_object(fd)?;

Expand All @@ -300,6 +362,11 @@ pub(crate) fn write(fd: FileDescriptor, buf: &[u8]) -> io::Result<usize> {
block_on(obj.write(buf), None)
}

pub(crate) fn truncate(fd: FileDescriptor, length: usize) -> io::Result<()> {
let obj = get_object(fd)?;
block_on(obj.truncate(length), None)
}

async fn poll_fds(fds: &mut [PollFd]) -> io::Result<u64> {
future::poll_fn(|cx| {
let mut counter: u64 = 0;
Expand Down
117 changes: 116 additions & 1 deletion src/fs/fuse.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ use crate::drivers::virtio::virtqueue::error::VirtqError;
use crate::errno::Errno;
use crate::executor::block_on;
use crate::fd::PollEvent;
use crate::fs::fuse::ops::SetAttrValidFields;
use crate::fs::{
self, AccessPermission, DirectoryEntry, FileAttr, NodeKind, ObjectInterface, OpenOption,
SeekWhence, VfsNode,
Expand Down Expand Up @@ -63,11 +64,12 @@ pub(crate) mod ops {
use alloc::boxed::Box;
use alloc::ffi::CString;

use fuse_abi::linux;
use fuse_abi::linux::*;

use super::Cmd;
use crate::fd::PollEvent;
use crate::fs::SeekWhence;
use crate::fs::{FileAttr, SeekWhence};

#[repr(C)]
#[derive(Debug, Default, Copy, Clone, Hash, PartialEq, Eq)]
Expand Down Expand Up @@ -270,6 +272,76 @@ pub(crate) mod ops {
}
}

#[derive(Debug)]
pub(crate) struct Setattr;

impl Op for Setattr {
const OP_CODE: fuse_opcode = fuse_opcode::FUSE_SETATTR;
type InStruct = fuse_setattr_in;
type InPayload = ();
type OutStruct = fuse_attr_out;
type OutPayload = ();
}

bitflags! {
#[derive(Debug, Copy, Clone, Default)]
pub struct SetAttrValidFields: u32 {
const FATTR_MODE = linux::FATTR_MODE;
const FATTR_UID = linux::FATTR_UID;
const FATTR_GID = linux::FATTR_GID;
const FATTR_SIZE = linux::FATTR_SIZE;
const FATTR_ATIME = linux::FATTR_ATIME;
const FATTR_MTIME = linux::FATTR_MTIME;
const FATTR_FH = linux::FATTR_FH;
const FATTR_ATIME_NOW = linux::FATTR_ATIME_NOW;
const FATTR_MTIME_NOW = linux::FATTR_MTIME_NOW;
const FATTR_LOCKOWNER = linux::FATTR_LOCKOWNER;
const FATTR_CTIME = linux::FATTR_CTIME;
const FATTR_KILL_SUIDGID = linux::FATTR_KILL_SUIDGID;
}
}

impl Setattr {
pub(crate) fn create(
nid: u64,
fh: u64,
attr: FileAttr,
valid_attr: SetAttrValidFields,
) -> (Cmd<Self>, u32) {
let cmd = Cmd::new(
nid,
fuse_setattr_in {
valid: valid_attr
.difference(
// Remove unsupported attributes
SetAttrValidFields::FATTR_LOCKOWNER,
)
.bits(),
padding: 0,
fh,

// Fuse attributes mapping: https://github.com/libfuse/libfuse/blob/fc1c8da0cf8a18d222cb1feed0057ba44ea4d18f/lib/fuse_lowlevel.c#L105
size: attr.st_size as u64,
atime: attr.st_atim.tv_sec as u64,
atimensec: attr.st_atim.tv_nsec as u32,
mtime: attr.st_ctim.tv_sec as u64,
mtimensec: attr.st_ctim.tv_nsec as u32,
ctime: attr.st_ctim.tv_sec as u64,
ctimensec: attr.st_ctim.tv_nsec as u32,
mode: attr.st_mode.bits(),
unused4: 0,
uid: attr.st_uid,
gid: attr.st_gid,
unused5: 0,

lock_owner: 0, // unsupported
},
);

(cmd, 0)
}
}

#[derive(Debug)]
pub(crate) struct Readlink;

Expand Down Expand Up @@ -758,6 +830,23 @@ impl FuseFileHandleInner {
Err(Errno::Io)
}
}

fn set_attr(&mut self, attr: FileAttr, valid: SetAttrValidFields) -> io::Result<FileAttr> {
debug!("FUSE setattr");
if let (Some(nid), Some(fh)) = (self.fuse_nid, self.fuse_fh) {
let (cmd, rsp_payload_len) = ops::Setattr::create(nid, fh, attr, valid);
let rsp = get_filesystem_driver()
.ok_or(Errno::Nosys)?
.lock()
.send_command(cmd, rsp_payload_len)?;
if rsp.headers.out_header.error < 0 {
return Err(Errno::Io);
}
Ok(rsp.headers.op_header.attr.into())
} else {
Err(Errno::Io)
}
}
}

impl Drop for FuseFileHandleInner {
Expand Down Expand Up @@ -804,6 +893,32 @@ impl ObjectInterface for FuseFileHandle {
async fn fstat(&self) -> io::Result<FileAttr> {
self.0.lock().await.fstat()
}

async fn truncate(&self, size: usize) -> io::Result<()> {
let attr = FileAttr {
st_size: size.try_into().unwrap(),
..FileAttr::default()
};

self.0
.lock()
.await
.set_attr(attr, SetAttrValidFields::FATTR_SIZE)
.map(|_| ())
}

async fn chmod(&self, access_permission: AccessPermission) -> io::Result<()> {
let attr = FileAttr {
st_mode: access_permission,
..FileAttr::default()
};

self.0
.lock()
.await
.set_attr(attr, SetAttrValidFields::FATTR_MODE)
.map(|_| ())
}
}

impl Clone for FuseFileHandle {
Expand Down
13 changes: 13 additions & 0 deletions src/fs/mem.rs
Original file line number Diff line number Diff line change
Expand Up @@ -256,6 +256,19 @@ impl ObjectInterface for RamFileInterface {
let guard = self.inner.read().await;
Ok(guard.attr)
}

async fn truncate(&self, size: usize) -> io::Result<()> {
let mut guard = self.inner.write().await;
guard.data.resize(size, 0);
guard.attr.st_size = size as i64;
Ok(())
}

async fn chmod(&self, access_permission: AccessPermission) -> io::Result<()> {
let mut guard = self.inner.write().await;
guard.attr.st_mode = access_permission;
Ok(())
}
}

impl RamFileInterface {
Expand Down
Loading