Skip to content

Commit 141d352

Browse files
committed
feat: add access / faccessat function and syscall
1 parent d714071 commit 141d352

File tree

2 files changed

+126
-2
lines changed

2 files changed

+126
-2
lines changed

src/fd/mod.rs

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,52 @@ bitflags! {
6969
}
7070
}
7171

72+
bitflags! {
73+
/// Options for checking file permissions or existence
74+
#[derive(Debug, Copy, Clone, Default, Eq, PartialEq)]
75+
pub struct AccessOption: i32 {
76+
/// Test for read permission
77+
const R_OK = 4;
78+
/// Test for write permission
79+
const W_OK = 2;
80+
/// Test for execution permission
81+
const X_OK = 1;
82+
/// Test for existence
83+
const F_OK = 0;
84+
}
85+
}
86+
87+
impl AccessOption {
88+
/// Verifies if the current access options are all valid for the provided file access permissions
89+
pub fn can_access(&self, access_permissions: AccessPermission) -> bool {
90+
if self.contains(AccessOption::R_OK)
91+
&& !access_permissions.contains(AccessPermission::S_IRUSR)
92+
&& !access_permissions.contains(AccessPermission::S_IRGRP)
93+
&& !access_permissions.contains(AccessPermission::S_IROTH)
94+
{
95+
return false;
96+
}
97+
98+
if self.contains(AccessOption::W_OK)
99+
&& !access_permissions.contains(AccessPermission::S_IWUSR)
100+
&& !access_permissions.contains(AccessPermission::S_IWGRP)
101+
&& !access_permissions.contains(AccessPermission::S_IWOTH)
102+
{
103+
return false;
104+
}
105+
106+
if self.contains(AccessOption::X_OK)
107+
&& !access_permissions.contains(AccessPermission::S_IXUSR)
108+
&& !access_permissions.contains(AccessPermission::S_IXGRP)
109+
&& !access_permissions.contains(AccessPermission::S_IXOTH)
110+
{
111+
return false;
112+
}
113+
114+
true
115+
}
116+
}
117+
72118
bitflags! {
73119
/// File status flags.
74120
#[derive(Debug, Copy, Clone, Default)]

src/syscalls/mod.rs

Lines changed: 80 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,8 +22,8 @@ pub use self::tasks::*;
2222
pub use self::timer::*;
2323
use crate::executor::block_on;
2424
use crate::fd::{
25-
self, AccessPermission, EventFlags, FileDescriptor, OpenOption, PollFd, dup_object,
26-
dup_object2, get_object, isatty, remove_object,
25+
self, AccessOption, AccessPermission, EventFlags, FileDescriptor, OpenOption, PollFd,
26+
dup_object, dup_object2, get_object, isatty, remove_object,
2727
};
2828
use crate::fs::{self, FileAttr, SeekWhence};
2929
#[cfg(all(target_os = "none", not(feature = "common-os")))]
@@ -390,6 +390,84 @@ pub unsafe extern "C" fn sys_umask(umask: u32) -> u32 {
390390

391391
#[hermit_macro::system(errno)]
392392
#[unsafe(no_mangle)]
393+
pub unsafe extern "C" fn sys_faccessat(
394+
dirfd: FileDescriptor,
395+
name: *const c_char,
396+
_mode: i32,
397+
flags: i32,
398+
) -> i32 {
399+
let access_option = AccessOption::from_bits_truncate(flags);
400+
if access_option.bits() != flags {
401+
return -crate::errno::EINVAL;
402+
}
403+
404+
let Ok(name) = unsafe { CStr::from_ptr(name) }.to_str() else {
405+
return -crate::errno::EINVAL;
406+
};
407+
408+
const AT_SYMLINK_NOFOLLOW: i32 = 0x100;
409+
const AT_FDCWD: i32 = -100;
410+
411+
let stat = if name.starts_with("/") || dirfd == AT_FDCWD {
412+
let no_follow: bool = (flags & AT_SYMLINK_NOFOLLOW) != 0;
413+
414+
if no_follow {
415+
crate::fs::read_stat(name)
416+
} else {
417+
crate::fs::read_lstat(name)
418+
}
419+
} else {
420+
warn!("faccessat with directory relative to fd is not implemented!");
421+
return -crate::errno::ENOSYS;
422+
};
423+
424+
if let Err(e) = stat {
425+
return -i32::from(e);
426+
}
427+
428+
let Ok(stat) = stat else { unreachable!() };
429+
if access_option.can_access(stat.st_mode) {
430+
0
431+
} else {
432+
-crate::errno::EACCES
433+
}
434+
}
435+
436+
#[hermit_macro::system]
437+
#[unsafe(no_mangle)]
438+
pub unsafe extern "C" fn sys_access(name: *const c_char, flags: i32) -> i32 {
439+
// This is an implementation of libc's access, not of linux's faccessat
440+
// See https://linux.die.net/man/2/faccessat for differences
441+
442+
let access_option = AccessOption::from_bits_truncate(flags);
443+
if access_option.bits() != flags {
444+
return -crate::errno::EINVAL;
445+
}
446+
447+
if access_option.contains(AccessOption::F_OK) && access_option != AccessOption::F_OK {
448+
return -crate::errno::EINVAL;
449+
}
450+
451+
let Ok(name) = unsafe { CStr::from_ptr(name) }.to_str() else {
452+
return -crate::errno::EINVAL;
453+
};
454+
let stat = crate::fs::read_lstat(name);
455+
456+
if let Err(e) = stat {
457+
return -i32::from(e);
458+
}
459+
460+
let Ok(stat) = stat else { unreachable!() };
461+
462+
if access_option.can_access(stat.st_mode) {
463+
0
464+
} else {
465+
-crate::errno::EACCES
466+
}
467+
}
468+
469+
#[hermit_macro::system]
470+
#[unsafe(no_mangle)]
393471
pub extern "C" fn sys_close(fd: FileDescriptor) -> i32 {
394472
let obj = remove_object(fd);
395473
obj.map_or_else(|e| -i32::from(e), |_| 0)

0 commit comments

Comments
 (0)