8000 Fix dup/dup2 + Fix fcntl + New fdtables API by Yaxuan-w · Pull Request #117 · Lind-Project/lind-wasm · GitHub
[go: up one dir, main page]
More Web Proxy on the site http://driver.im/
Skip to content

Fix dup/dup2 + Fix fcntl + New fdtables API #117

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 14 commits into from
Apr 21, 2025
9 changes: 1 addition & 8 deletions src/RawPOSIX/src/safeposix/dispatcher.rs
Original file line number Diff line number Diff line change
Expand Up @@ -506,16 +506,9 @@ pub fn lind_syscall_api(
DUP_SYSCALL => {
let fd = arg1 as i32;

// Convert second argument to Option<i32> if it's within valid range
let fd2: Option<i32> = if arg1 <= i32::MAX as u64 {
Some(arg1 as i32)
} else {
None
};

// Perform dup operation through cage implementation
// File descriptor validation handled by cage layer
interface::cagetable_getref(cageid).dup_syscall(fd, fd2)
interface::cagetable_getref(cageid).dup_syscall(fd)
}

DUP2_SYSCALL => {
Expand Down
257 changes: 194 additions & 63 deletions src/RawPOSIX/src/safeposix/syscalls/fs_calls.rs
8000
Original file line number Diff line number Diff line change
Expand Up @@ -775,48 +775,75 @@ impl Cage {
}

//------------------------------------DUP & DUP2 SYSCALLS------------------------------------
/*
* dup() / dup2() will return a file descriptor
* Mapping a new virtual fd and kernel fd that libc::dup returned
* Then return virtual fd
*/
pub fn dup_syscall(&self, virtual_fd: i32, _start_desc: Option<i32>) -> i32 {
/// Unix / Linux Reference: https://man7.org/linux/man-pages/man2/dup.2.html
///
/// Since the two file descriptors refer to the same open file description, they share file offset
/// and file status flags. Then, in RawPOSIX, we mapped duplicated file descriptor to same underlying
/// kernel fd.
///
/// ## Arguments:
/// - `virtual_fd`: virtual file descriptor
///
/// ## Return type:
/// - `0` on success.
/// - `-1` on failure, with `errno` set appropriately.
pub fn dup_syscall(&self, virtual_fd: i32) -> i32 {
if virtual_fd < 0 {
return syscall_error(Errno::EBADF, "dup", "Bad File Descriptor");
}
// Get underlying kernel fd
let wrappedvfd = fdtables::translate_virtual_fd(self.cageid, virtual_fd as u64);
if wrappedvfd.is_err() {
return syscall_error(Errno::EBADF, "dup", "Bad File Descriptor");
}
let vfd = wrappedvfd.unwrap();
let ret_kernelfd = unsafe { libc::dup(vfd.underfd as i32) };
// Request another virtual fd to refer to same underlying kernel fd as `virtual_fd`
// from input
let ret_virtualfd =
fdtables::get_unused_virtual_fd(self.cageid, vfd.fdkind, ret_kernelfd as u64, false, 0)
fdtables::get_unused_virtual_fd(self.cageid, vfd.fdkind, vfd.underfd, false, 0)
.unwrap();
return ret_virtualfd as i32;
}

/*
*/
/// dup2() performs the same task as dup(), so we utilize dup() here and mapping underlying kernel
/// fd with specific `new_virutalfd`
///
/// ## Arguments:
/// - `old_virtualfd`: original virtual file descriptor
/// - `new_virtualfd`: specified new virtual file descriptor
///
/// ## Return type:
/// - `0` on success.
/// - `-1` on failure, with `errno` set appropriately.
pub fn dup2_syscall(&self, old_virtualfd: i32, new_virtualfd: i32) -> i32 {
// Validate both virtual fds
if old_virtualfd < 0 || new_virtualfd < 0 {
return syscall_error(Errno::EBADF, "dup", "Bad File Descriptor");
} else if old_virtualfd == new_virtualfd {
// Does nothing
return new_virtualfd;
}

// If the file descriptor newfd was previously open, it is closed before being reused; the
// close is performed silently (i.e., any errors during the close are not reported by dup2()).
// This step is handled inside `fdtables`
match fdtables::translate_virtual_fd(self.cageid, old_virtualfd as u64) {
Ok(old_vfd) => {
let new_kernelfd = unsafe { libc::dup(old_vfd.underfd as i32) };
// Map new kernel fd with provided kernel fd
let _ret_kernelfd = unsafe { libc::dup2(old_vfd.underfd as i32, new_kernelfd) };
// Request another virtual fd to refer to same underlying kernel fd as `virtual_fd`
// from input.
// The two file descriptors do not share file descriptor flags (the
// close-on-exec flag). The close-on-exec flag (FD_CLOEXEC; see fcntl_syscall())
// for the duplicate descriptor is off
let _ = fdtables::get_specific_virtual_fd(
self.cageid,
new_virtualfd as u64,
old_vfd.fdkind,
new_kernelfd as u64,
old_vfd.underfd,
false,
old_vfd.perfdinfo,
)
.unwrap();

return new_virtualfd;
}
Err(_e) => {
Expand Down Expand Up @@ -845,60 +872,134 @@ impl Cage {
}

//------------------------------------FCNTL SYSCALL------------------------------------
/*
* For a successful call, the return value depends on the operation:

F_DUPFD
The new file descriptor.

F_GETFD
Value of file descriptor flags.

F_GETFL
Value of file status flags.

F_GETLEASE
Type of lease held on file descriptor.

F_GETOWN
Value of file descriptor owner.

F_GETSIG
Value of signal sent when read or write becomes possible,
or zero for traditional SIGIO behavior.

F_GETPIPE_SZ, F_SETPIPE_SZ
The pipe capacity.

F_GET_SEALS
A bit mask identifying the seals that have been set for
the inode referred to by fd.

All other commands
Zero.

On error, -1 is returned
*/
/// Reference: https://man7.org/linux/man-pages/man2/fcntl.2.html
///
/// Due to the design of `fdtables` library, different virtual fds created by `dup`/`dup2` are
/// actually refer to the same underlying kernel fd. Therefore, in `fcntl_syscall` we need to
/// handle the cases of `F_DUPFD`, `F_DUPFD_CLOEXEC`, `F_GETFD`, and `F_SETFD` separately.
///
/// Among these, `F_DUPFD` and `F_DUPFD_CLOEXEC` cannot directly use the `dup_syscall` because,
/// in `fcntl`, the duplicated fd is assigned to the lowest available number starting from `arg`,
/// whereas the `dup_syscall` does not have this restriction and instead assigns the lowest
/// available fd number globally.
///
/// Additionally, `F_DUPFD_CLOEXEC` and `F_SETFD` require updating the fd flag information
/// (`O_CLOEXEC`) in fdtables after modifying the underlying kernel fd.
///
/// For all other command operations, after translating the virtual fd to the corresponding
/// kernel fd, they are redirected to the kernel `fcntl` syscall.
///
/// ## Arguments
/// virtual_fd: virtual file descriptor
/// cmd: The operation
/// arg: an optional third argument. Whether or not this argument is required is determined by op.
///
/// ## Return Type
/// The return value is related to the operation determined by `cmd` argument.
///
/// For a successful call, the return value depends on the operation:
/// `F_DUPFD`: The new file descriptor.
/// `F_GETFD`: Value of file descriptor flags.
/// `F_GETFL`: Value of file status flags.
/// `F_GETLEASE`: Type of lease held on file descriptor.
/// `F_GETOWN`: Value of file descriptor owner.
/// `F_GETSIG`: Value of signal sent when read or write becomes possible, or zero for traditional SIGIO behavior.
/// `F_GETPIPE_SZ`, `F_SETPIPE_SZ`: The pipe capacity.
/// `F_GET_SEALS`: A bit mask identifying the seals that have been set for the inode referred to by fd.
/// All other commands: Zero.
/// On error, -1 is returned
///
/// TODO: `F_GETOWN`, `F_SETOWN`, `F_GETOWN_EX`, `F_SETOWN_EX`, `F_GETSIG`, and `F_SETSIG` are used to manage I/O availability signals.
pub fn fcntl_syscall(&self, virtual_fd: i32, cmd: i32, arg: i32) -> i32 {
match (cmd, arg) {
(F_GETOWN, ..) => {
//
1000
// Duplicate the file descriptor `virtual_fd` using the lowest-numbered
// available file descriptor greater than or equal to `arg`. The operation here
// is quite similar to `dup_syscall`, for specific operation explanation, see
// comments on `dup_syscall`.
(F_DUPFD, arg) => {
// Get fdtable entry
let vfd = match _fcntl_helper(self.cageid, virtual_fd) {
Ok(entry) => entry,
Err(e) => return syscall_error(e, "fcntl", "Bad File Descriptor"),
};
// Get lowest-numbered available file descriptor greater than or equal to `arg`
match fdtables::get_unused_virtual_fd_from_startfd(
self.cageid,
vfd.fdkind,
vfd.underfd,
false,
0,
arg as u64,
) {
Ok(new_vfd) => return new_vfd as i32,
Err(_) => return syscall_error(Errno::EBADF, "fcntl", "Bad File Descriptor"),
}
}
(F_SETOWN, arg) if arg >= 0 => 0,
_ => {
let wrappedvfd = fdtables::translate_virtual_fd(self.cageid, virtual_fd as u64);
if wrappedvfd.is_err() {
return syscall_error(Errno::EBADF, "fcntl", "Bad File Descriptor");
// As for `F_DUPFD`, but additionally set the close-on-exec flag
// for the duplicate file descriptor.
(F_DUPFD_CLOEXEC, arg) => {
// Get fdtable entry
let vfd = match _fcntl_helper(self.cageid, virtual_fd) {
Ok(entry) => entry,
Err(e) => return syscall_error(e, "fcntl", "Bad File Descriptor"),
};
// Get lowest-numbered available file descriptor greater than or equal to `arg`
// and set the `O_CLOEXEC` flag. This matches the POSIX system call behavior
match fdtables::get_unused_virtual_fd_from_startfd(
self.cageid,
vfd.fdkind,
vfd.underfd,
true,
0,
arg as u64,
) {
Ok(new_vfd) => return new_vfd as i32,
Err(_) => return syscall_error(Errno::EBADF, "fcntl", "Bad File Descriptor"),
}
let vfd = wrappedvfd.unwrap();
if cmd == libc::F_DUPFD {
match arg {
n if n < 0 => return syscall_error(Errno::EINVAL, "fcntl", "op is F_DUPFD and arg is negative or is greater than the maximum allowable value"),
0..=1024 => return self.dup2_syscall(virtual_fd, arg),
_ => return syscall_error(Errno::EMFILE, "fcntl", "op is F_DUPFD and the per-process limit on the number of open file descriptors has been reached")
}
}
// Return (as the function result) the file descriptor flags.
(F_GETFD, ..) => {
// Get fdtable entry
let vfd = match _fcntl_helper(self.cageid, virtual_fd) {
Ok(entry) => entry,
Err(e) => return syscall_error(e, "fcntl", "Bad File Descriptor"),
};
return vfd.should_cloexec as i32;
}
// Set the file descriptor flags to the value specified by arg.
(F_SETFD, arg) => {
// Get fdtable entry
let vfd = match _fcntl_helper(self.cageid, virtual_fd) {
Ok(entry) => entry,
Err(e) => return syscall_error(e, "fcntl", "Bad File Descriptor"),
};
// Set underlying kernel fd flag
let ret = unsafe { libc::fcntl(vfd.underfd as i32, cmd, arg) };
if ret < 0 {
let errno = get_errno();
return handle_errno(errno, "fcntl");
}
// Set virtual fd flag
let cloexec_flag: bool = arg != 0;
match fdtables::set_cloexec(self.cageid, virtual_fd as u64, cloexec_flag) {
Ok(_) => return 0,
Err(_e) => return syscall_error(Errno::EBADF, "fcntl", "Bad File Descriptor"),
}
}
// Return (as the function result) the process ID or process
// group ID currently receiving SIGIO and SIGURG signals for
// events on file descriptor fd.
(F_GETOWN, ..) => DEFAULT_GID as i32,
// Set the process ID or process group ID that will receive
// SIGIO and SIGURG signals for events on the file descriptor
// fd.
(F_SETOWN, arg) if arg >= 0 => 0,
_ => {
// Get fdtable entry
let vfd = match _fcntl_helper(self.cageid, virtual_fd) {
Ok(entry) => entry,
Err(e) => return syscall_error(e, "fcntl", "Bad File Descriptor"),
};
let ret = unsafe { libc::fcntl(vfd.underfd as i32, cmd, arg) };
if ret < 0 {
let errno = get_errno();
Expand Down Expand Up @@ -1566,3 +1667,33 @@ pub fn kernel_close(fdentry: fdtables::FDTableEntry, _count: u64) {
panic!("kernel_close failed with errno: {:?}", errno);
}
}

/// This function will be different in new code base (when splitting out type conversion function)
/// since the conversion from u64 -> i32 in negative number will be different. These lines are repeated
/// in 5 out of 6 fcntl_syscall cases, so wrapped these loc into helper functions to make code cleaner.
///
/// ## Arguments
/// cageid: cage ID associate with virtual file descriptor
/// virtual_fd: virtual file descriptor
///
/// ## Return Type
/// On success:
/// Return corresponding FDTableEntry that contains
/// (1) underlying kernel fd.
/// (2) file descriptor kind.
/// (3) O_CLOEXEC flag.
/// (4) file descriptor specific extra information.
///
/// On error:
/// Return error num EBADF(Bad File Descriptor)
pub fn _fcntl_helper(cageid: u64, virtual_fd: i32) -> Result<fdtables::FDTableEntry, Errno> {
if virtual_fd < 0 {
return Err(Errno::EBADF);
}
// Get underlying kernel fd
let wrappedvfd = fdtables::translate_virtual_fd(cageid, virtual_fd as u64);
if wrappedvfd.is_err() {
return Err(Errno::EBADF);
}
Ok(wrappedvfd.unwrap())
}
24 changes: 24 additions & 0 deletions src/fdtables/docs/get_unused_virtual_fd_from_startfd.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
Get a virtualfd mapping to put an item into the fdtable.

This function requests an unused virtual FD starting from a specific position, as specified by the arg parameter. It behaves similarly to `get_unused_virtual_fd`, but starts the search at `arg` instead of `0`.

This is intended for use with `fcntl()` commands like `F_DUPFD` and `F_DUPFD_CLOEXEC`

# Panics
if the cageid does not exist

# Errors
if the cage has used EMFILE virtual descriptors already, return EMFILE

# Example
```
# use fdtables::*;
# let cage_id = threei::TESTING_CAGEID;
# let underfd: u64 = 10;
# let fdkind: u32 = 0;
# let arg: u64 = 2;
// Should not error... Ideally the `my_virt_fd` should be 2 in this case
let my_virt_fd = get_unused_virtual_fd_from_startfd(cage_id, fdkind, underfd, false, 0, arg).unwrap();
// Check that you get the real fd back here...
assert_eq!(underfd,translate_virtual_fd(cage_id, my_virt_fd).unwrap().underfd);
```
41 changes: 41 additions & 0 deletions src/fdtables/src/dashmaparrayglobal.rs
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,47 @@ pub fn get_unused_virtual_fd(
Err(threei::Errno::EMFILE as u64)
}

/// This is used to request an unused fd from specific starting position. This is
/// similar to `get_unused_virtual_fd` except this function starts from a specific
/// starting position mentioned by `arg` arguments. This will be used for `fcntl`.
#[doc = include_str!("../docs/get_unused_virtual_fd_from_startfd.md")]
pub fn get_unused_virtual_fd_from_startfd(
cageid: u64,
fdkind: u32,
underfd: u64,
should_cloexec: bool,
perfdinfo: u64,
arg: u64,
) -> Result<u64, threei::RetVal> {

assert!(FDTABLE.contains_key(&cageid),"Unknown cageid in fdtable access");
// Set up the entry so it has the right info...
// Note, a HashMap stores its data on the heap! No need to box it...
// https://doc.rust-lang.org/book/ch08-03-hash-maps.html#creating-a-new-hash-map
let myentry = FDTableEntry {
fdkind,
underfd,
should_cloexec,
perfdinfo,
};

let mut myfdrow = FDTABLE.get_mut(&cageid).unwrap();

// Check the fds in order.
for fdcandidate in arg..FD_PER_PROCESS_MAX {
// FIXME: This is likely very slow. Should do something smarter...
if myfdrow[fdcandidate as usize].is_none() {
// I just checked. Should not be there...
myfdrow[fdcandidate as usize] = Some(myentry);
_increment_fdcount(myentry);
return Ok(fdcandidate);
}
}

// I must have checked all fds and failed to find one open. Fail!
Err(threei::Errno::EMFILE as u64)
}

// This is used for things like dup2, which need a specific fd...
// If the requested_virtualfd is used, I close it...
#[doc = include_str!("../docs/get_specific_virtual_fd.md")]
Expand Down
Loading
0