Skip to content
Merged
Show file tree
Hide file tree
Changes from 4 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
117 changes: 114 additions & 3 deletions src/safeposix/syscalls/fs_calls.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1897,7 +1897,44 @@ impl Cage {
0 //chdir has succeeded!;
}

//------------------------------------DUP & DUP2 SYSCALLS------------------------------------
///##------------------------------------DUP & DUP2 SYSCALLS------------------------------------
/// ## `dup_syscall`
///
/// ### Description
/// This function duplicates a file descriptor. It creates a new file
/// descriptor that refers to the same open file description as the original file descriptor.
/// * Finding the Next Available File Descriptor: If `start_desc` is provided and it is already in use, the function
/// will continue searching for the next available file descriptor starting from `start_desc`. If no file
/// descriptors are available, it will return an error (`ENFILE`).
/// * If `fd` is equal to `start_fd`, the function returns `start_fd` as the new file
/// descriptor. This is because in this scenario, the original and new file descriptors would point to the same
/// file description.
/// * The `_dup2_helper` function is called to perform the actual file descriptor duplication, handling the
/// allocation of a new file descriptor, updating the file descriptor table, and incrementing the reference count
/// of the file object.
/// * The function modifies the global `filedescriptortable` array, adding a new entry for the
/// duplicated file descriptor. It also increments the reference count of the file object associated with the
/// original file descriptor.
/// * The `false` argument passed to `_dup2_helper` indicates that this call is from the `dup_syscall` function,
/// not the `dup2_syscall` function.
///
/// ### Function Arguments
/// * `fd`: The original file descriptor to duplicate.
/// * `start_desc`: An optional starting file descriptor number. If provided, the new file descriptor will be
/// assigned the first available file descriptor number starting from this value. If not provided, it defaults to
/// `STARTINGFD`,which is the minimum designated file descriptor value for new file descriptors.
///
/// ### Returns
/// * The new file descriptor on success.
/// * `EBADF`: If the original file descriptor is invalid.
/// * `ENFILE`: If there are no available file descriptors.
///
/// ### Errors
/// * `EBADF(9)`: If the original file descriptor is invalid.
/// * `ENFILE(23)`: If there are no available file descriptors.
Comment thread
rupeshkoushik07 marked this conversation as resolved.
/// ###Panics
/// * There are no panics for this syscall
///[dup(2)](https://man7.org/linux/man-pages/man2/dup.2.html)

pub fn dup_syscall(&self, fd: i32, start_desc: Option<i32>) -> i32 {
//if a starting fd was passed, then use that as the starting point, but otherwise, use the designated minimum of STARTINGFD
Expand All @@ -1911,7 +1948,11 @@ impl Cage {
} //if the file descriptors are equal, return the new one

// get the filedesc_enum
let checkedfd = self.get_filedescriptor(fd).unwrap();
// Attempt to get the file descriptor; handle error if it does not exist
let checkedfd = match self.get_filedescriptor(fd) {
Ok(fd) => fd,
Err(_) => return syscall_error(Errno::EBADF, "dup", "Invalid old file descriptor."),
};
let filedesc_enum = checkedfd.write();
let filedesc_enum = if let Some(f) = &*filedesc_enum {
f
Expand All @@ -1923,6 +1964,34 @@ impl Cage {
return Self::_dup2_helper(&self, filedesc_enum, start_fd, false);
}

/// ## `dup2_syscall`
///
/// ### Description
/// This function implements the `dup2` system call, which duplicates a file descriptor and assigns it to a new file
/// descriptor number. If the new file descriptor already exists, it is closed before the duplication takes place.
/// * File Descriptor Reuse: If the new file descriptor (`newfd`) is already open, the function will first close the
/// existing file descriptor silently (without returning an error) before allocating a new file descriptor and
/// updating the file descriptor table.
/// * If `oldfd` and `newfd` are the same, the function returns `newfd` without closing it.
/// This is because in this scenario, the original and new file descriptors would already point to the same file
/// description.
/// * the global `filedescriptortable` array, replacing the entry for the
/// new file descriptor with a new entry for the duplicated file descriptor. It also increments the reference count of the
/// file object associated with the original file descriptor.
///
/// ### Function Arguments
/// * `oldfd`: The original file descriptor to duplicate.
/// * `newfd`: The new file descriptor number to assign to the duplicated file descriptor.
///
/// ### Returns
/// * The new file descriptor on success.
///
/// ### Errors
/// * `EBADF(9)`: If the original file descriptor (`oldfd`) is invalid or the new file descriptor (`newfd`) number is out of range.
Comment thread
rupeshkoushik07 marked this conversation as resolved.
/// ###Panics
/// * There are no panics for this syscall
///[dup2(2)](https://linux.die.net/man/2/dup2)

pub fn dup2_syscall(&self, oldfd: i32, newfd: i32) -> i32 {
//checking if the new fd is out of range
if newfd >= MAXFD || newfd < 0 {
Expand All @@ -1938,7 +2007,10 @@ impl Cage {
} //if the file descriptors are equal, return the new one

// get the filedesc_enum
let checkedfd = self.get_filedescriptor(oldfd).unwrap();
let checkedfd = match self.get_filedescriptor(oldfd) {
Ok(fd) => fd,
Err(_) => return syscall_error(Errno::EBADF, "dup2", "Invalid old file descriptor."),
};
let filedesc_enum = checkedfd.write();
let filedesc_enum = if let Some(f) = &*filedesc_enum {
f
Expand All @@ -1950,6 +2022,41 @@ impl Cage {
return Self::_dup2_helper(&self, filedesc_enum, newfd, true);
}

/// ## `_dup2_helper`
///
/// ### Description
/// This helper function performs the actual file descriptor duplication process for both `dup` and `dup2` system calls.
/// It handles the allocation of a new file descriptor, updates the file descriptor table, and increments the reference count of the
/// associated file object.
/// * Duplication from `dup2_syscall`: If `fromdup2` is true, the function first closes the existing file descriptor
/// at `newfd` (if any) before allocating a new file descriptor and updating the file descriptor table.
/// * Duplication from `dup_syscall`: If `fromdup2` is false, the function allocates a new file descriptor, finds the
/// first available file descriptor number starting from `newfd`, and updates the file descriptor table.
/// * Reference Counting: The function increments the reference count of the file object associated with the original file
/// descriptor. This ensures that the file object is not deleted until all its associated file descriptors are closed.
/// * Socket Handling: For domain sockets, the function increments the reference count of both the send and receive pipes
/// associated with the socket.
/// * Stream Handling: Streams are not currently supported for duplication
/// * Unhandled File Types: If the file descriptor is associated with a file type that is not handled by the function (i.e.,
/// not a File, Pipe, Socket, or Stream), the function returns an error (`EACCES`).
/// * The function does not handle streams.
/// * Socket Handling: If the file descriptor is associated with a socket, the function handles domain sockets differently
/// by incrementing the reference count of both the send and receive pipes.
/// ### Function Arguments
/// * `self`: A reference to the `FsCalls` struct, which contains the file descriptor table and other system-related data.
/// * `filedesc_enum`: A reference to the `FileDescriptor` object representing the file descriptor to be duplicated.
/// * `newfd`: The new file descriptor number to assign to the duplicated file descriptor.
/// * `fromdup2`: A boolean flag indicating whether the call is from `dup2_syscall` (true) or `dup_syscall` (false).
///
/// ### Returns
/// * The new file descriptor on success.
///
/// ### Errors
/// * `ENFILE(23)`: If there are no available file descriptors.
/// * `EACCES(13)`: If the file descriptor cannot be duplicated.
/// ###Panics
/// * If the file descriptor is associated with a socket, and the inode does not match the file descriptor.

pub fn _dup2_helper(&self, filedesc_enum: &FileDescriptor, newfd: i32, fromdup2: bool) -> i32 {
Comment thread
rupeshkoushik07 marked this conversation as resolved.
let (dupfd, mut dupfdguard) = if fromdup2 {
let mut fdguard = self.filedescriptortable[newfd as usize].write();
Expand All @@ -1966,6 +2073,8 @@ impl Cage {
} else {
let (newdupfd, guardopt) = self.get_next_fd(Some(newfd));
if newdupfd < 0 {
// The function allocates a new file descriptor and updates the file descriptor table,
// handling the potential for file descriptor table overflow (resulting in an `ENFILE` error).
return syscall_error(
Errno::ENFILE,
"dup2_helper",
Expand All @@ -1984,6 +2093,8 @@ impl Cage {
//incrementing the ref count so that when close is executed on the dup'd file
//the original file does not get a negative ref count
match *inodeobj {
// increments the reference count of the file object associated with the original file descriptor
// to ensure that the file object is not deleted until all its associated file descriptors are closed.
Inode::File(ref mut normalfile_inode_obj) => {
normalfile_inode_obj.refcount += 1;
}
Expand Down
95 changes: 87 additions & 8 deletions src/tests/fs_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@ pub mod fs_tests {

#[test]
pub fn ut_lind_fs_simple() {

//acquiring a lock on TESTMUTEX prevents other tests from running concurrently, and also performs clean env setup
let _thelock = setup::lock_and_init();

Expand Down Expand Up @@ -142,9 +141,9 @@ pub mod fs_tests {
pub fn ut_lind_fs_broken_close() {
//acquiring a lock on TESTMUTEX prevents other tests from running concurrently, and also performs clean env setup
let _thelock = setup::lock_and_init();

//testing a muck up with the inode table where the regular close does not work as intended

let cage = interface::cagetable_getref(1);

//write should work
Expand Down Expand Up @@ -477,6 +476,46 @@ pub mod fs_tests {
assert_eq!(cage.exit_syscall(EXIT_SUCCESS), EXIT_SUCCESS);
lindrustfinalize();
}
#[test]
Comment thread
rupeshkoushik07 marked this conversation as resolved.
fn ut_lind_fs_dup_invalid_fd() {
let _thelock = setup::lock_and_init();
let cage = interface::cagetable_getref(1);

// Open a file and get a valid file descriptor
let fd = cage.open_syscall("/testfile", O_CREAT | O_WRONLY, S_IRWXA);
assert_ne!(fd, -(Errno::ENOENT as i32));

// Close the file descriptor, making it invalid
assert_eq!(cage.close_syscall(fd), 0);

// Attempt to duplicate the invalid file descriptor
let new_fd = cage.dup_syscall(fd, None);
assert_eq!(new_fd, -(Errno::EBADF as i32));

assert_eq!(cage.exit_syscall(EXIT_SUCCESS), EXIT_SUCCESS);
lindrustfinalize();
}

#[test]
fn ut_lind_fs_dup_full_table() {
let _thelock = setup::lock_and_init();
let cage = interface::cagetable_getref(1);

// Open a large number of files to fill the file descriptor table
for i in 0..1024 {
let fd = cage.open_syscall(&format!("/testfile{}", i), O_CREAT | O_WRONLY, S_IRWXA);
assert_ne!(fd, -(Errno::ENOENT as i32));
}

// Attempt to duplicate a file descriptor, which should fail
let fd = cage.open_syscall("/testfile", O_CREAT | O_WRONLY, S_IRWXA);
assert_ne!(fd, -(Errno::ENOENT as i32));
let new_fd = cage.dup_syscall(fd, None);
assert_eq!(new_fd, -(Errno::EBADF as i32));

assert_eq!(cage.exit_syscall(EXIT_SUCCESS), EXIT_SUCCESS);
lindrustfinalize();
}

#[test]
pub fn ut_lind_fs_dup2() {
Expand Down Expand Up @@ -529,6 +568,47 @@ pub mod fs_tests {
lindrustfinalize();
}

#[test]
fn ut_lind_fs_dup2_invalid_fd() {
let _thelock = setup::lock_and_init();
let cage = interface::cagetable_getref(1);

// Open a file
let fd = cage.open_syscall("/testfile", O_CREAT | O_WRONLY, S_IRWXA);
assert_ne!(fd, -(Errno::ENOENT as i32));

// Close the file descriptor, making it invalid
assert_eq!(cage.close_syscall(fd), 0);

// Attempt to duplicate the invalid file descriptor
let new_fd = cage.dup2_syscall(fd, 5);
assert_eq!(new_fd, -(Errno::EBADF as i32));

assert_eq!(cage.exit_syscall(EXIT_SUCCESS), EXIT_SUCCESS);
lindrustfinalize();
}

#[test]
fn ut_lind_fs_dup2_full_table() {
let _thelock = setup::lock_and_init();
let cage = interface::cagetable_getref(1);

// Open a large number of files to fill the file descriptor table
for i in 0..1024 {
let fd = cage.open_syscall(&format!("/testfile{}", i), O_CREAT | O_WRONLY, S_IRWXA);
assert_ne!(fd, -(Errno::ENOENT as i32));
}

// Attempt to duplicate a file descriptor, which should fail
let fd = cage.open_syscall("/testfile", O_CREAT | O_WRONLY, S_IRWXA);
assert_ne!(fd, -(Errno::ENOENT as i32));
let new_fd = cage.dup2_syscall(fd, 5); // Try to duplicate to an existing fd
assert_eq!(new_fd, -(Errno::EBADF as i32));

assert_eq!(cage.exit_syscall(EXIT_SUCCESS), EXIT_SUCCESS);
lindrustfinalize();
}

#[test]
pub fn ut_lind_fs_fcntl_valid_args() {
//acquiring a lock on TESTMUTEX prevents other tests from running concurrently, and also performs clean env setup
Expand Down Expand Up @@ -564,7 +644,7 @@ pub mod fs_tests {
}

#[test]
pub fn ut_lind_fs_fcntl_invalid_args(){
pub fn ut_lind_fs_fcntl_invalid_args() {
//acquiring a lock on TESTMUTEX prevents other tests from running concurrently, and also performs clean env setup
let _thelock = setup::lock_and_init();

Expand Down Expand Up @@ -595,7 +675,7 @@ pub mod fs_tests {
}

#[test]
pub fn ut_lind_fs_fcntl_dup(){
pub fn ut_lind_fs_fcntl_dup() {
//acquiring a lock on TESTMUTEX prevents other tests from running concurrently, and also performs clean env setup
let _thelock = setup::lock_and_init();

Expand Down Expand Up @@ -670,7 +750,7 @@ pub mod fs_tests {
pub fn ut_lind_fs_ioctl_invalid_args() {
//acquiring a lock on TESTMUTEX prevents other tests from running concurrently, and also performs clean env setup
let _thelock = setup::lock_and_init();

let cage = interface::cagetable_getref(1);

//setting up two integer values (a zero value to test clearing nonblocking I/O behavior on
Expand Down Expand Up @@ -725,7 +805,7 @@ pub mod fs_tests {

assert_eq!(cage.exit_syscall(EXIT_SUCCESS), EXIT_SUCCESS);
lindrustfinalize();
}
}

#[test]
pub fn ut_lind_fs_fdflags() {
Expand Down Expand Up @@ -1464,7 +1544,6 @@ pub mod fs_tests {
//acquiring a lock on TESTMUTEX prevents other tests from running concurrently, and also performs clean env setup
let _thelock = setup::lock_and_init();


let cage1 = interface::cagetable_getref(1);
let pid1 = cage1.getpid_syscall();

Expand Down