Skip to content

Commit 5223a15

Browse files
rupeshkoushik07lind
authored andcommitted
Dup/dup2 syscall tests and comments (#285)
* add tests * update dup2 max * change error * update the comments and fromatting * update files/streams * add test * remove * remove * remove import
1 parent 2383aeb commit 5223a15

File tree

2 files changed

+245
-3
lines changed

2 files changed

+245
-3
lines changed

src/safeposix/syscalls/fs_calls.rs

Lines changed: 114 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2230,7 +2230,44 @@ impl Cage {
22302230
0 //chdir has succeeded!;
22312231
}
22322232

2233-
//------------------------------------DUP & DUP2 SYSCALLS------------------------------------
2233+
///##------------------------------------DUP & DUP2 SYSCALLS------------------------------------
2234+
/// ## `dup_syscall`
2235+
///
2236+
/// ### Description
2237+
/// This function duplicates a file descriptor. It creates a new file
2238+
/// descriptor that refers to the same open file description as the original file descriptor.
2239+
/// * Finding the Next Available File Descriptor: If `start_desc` is provided and it is already in use, the function
2240+
/// will continue searching for the next available file descriptor starting from `start_desc`. If no file
2241+
/// descriptors are available, it will return an error (`ENFILE`).
2242+
/// * If `fd` is equal to `start_fd`, the function returns `start_fd` as the new file
2243+
/// descriptor. This is because in this scenario, the original and new file descriptors would point to the same
2244+
/// file description.
2245+
/// * The `_dup2_helper` function is called to perform the actual file descriptor duplication, handling the
2246+
/// allocation of a new file descriptor, updating the file descriptor table, and incrementing the reference count
2247+
/// of the file object.
2248+
/// * The function modifies the global `filedescriptortable` array, adding a new entry for the
2249+
/// duplicated file descriptor. It also increments the reference count of the file object associated with the
2250+
/// original file descriptor.
2251+
/// * The `false` argument passed to `_dup2_helper` indicates that this call is from the `dup_syscall` function,
2252+
/// not the `dup2_syscall` function.
2253+
///
2254+
/// ### Function Arguments
2255+
/// * `fd`: The original file descriptor to duplicate.
2256+
/// * `start_desc`: An optional starting file descriptor number. If provided, the new file descriptor will be
2257+
/// assigned the first available file descriptor number starting from this value. If not provided, it defaults to
2258+
/// `STARTINGFD`,which is the minimum designated file descriptor value for new file descriptors.
2259+
///
2260+
/// ### Returns
2261+
/// * The new file descriptor on success.
2262+
/// * `EBADF`: If the original file descriptor is invalid.
2263+
/// * `ENFILE`: If there are no available file descriptors.
2264+
///
2265+
/// ### Errors
2266+
/// * `EBADF(9)`: If the original file descriptor is invalid.
2267+
/// * `ENFILE(23)`: If there are no available file descriptors.
2268+
/// ###Panics
2269+
/// * There are no panics for this syscall
2270+
///[dup(2)](https://man7.org/linux/man-pages/man2/dup.2.html)
22342271
22352272
pub fn dup_syscall(&self, fd: i32, start_desc: Option<i32>) -> i32 {
22362273
//if a starting fd was passed, then use that as the starting point, but
@@ -2245,7 +2282,11 @@ impl Cage {
22452282
} //if the file descriptors are equal, return the new one
22462283

22472284
// get the filedesc_enum
2248-
let checkedfd = self.get_filedescriptor(fd).unwrap();
2285+
// Attempt to get the file descriptor; handle error if it does not exist
2286+
let checkedfd = match self.get_filedescriptor(fd) {
2287+
Ok(fd) => fd,
2288+
Err(_) => return syscall_error(Errno::EBADF, "dup", "Invalid old file descriptor."),
2289+
};
22492290
let filedesc_enum = checkedfd.write();
22502291
let filedesc_enum = if let Some(f) = &*filedesc_enum {
22512292
f
@@ -2257,6 +2298,34 @@ impl Cage {
22572298
return Self::_dup2_helper(&self, filedesc_enum, start_fd, false);
22582299
}
22592300

2301+
/// ## `dup2_syscall`
2302+
///
2303+
/// ### Description
2304+
/// This function implements the `dup2` system call, which duplicates a file descriptor and assigns it to a new file
2305+
/// descriptor number. If the new file descriptor already exists, it is closed before the duplication takes place.
2306+
/// * File Descriptor Reuse: If the new file descriptor (`newfd`) is already open, the function will first close the
2307+
/// existing file descriptor silently (without returning an error) before allocating a new file descriptor and
2308+
/// updating the file descriptor table.
2309+
/// * If `oldfd` and `newfd` are the same, the function returns `newfd` without closing it.
2310+
/// This is because in this scenario, the original and new file descriptors would already point to the same file
2311+
/// description.
2312+
/// * the global `filedescriptortable` array, replacing the entry for the
2313+
/// new file descriptor with a new entry for the duplicated file descriptor. It also increments the reference count of the
2314+
/// file object associated with the original file descriptor.
2315+
///
2316+
/// ### Function Arguments
2317+
/// * `oldfd`: The original file descriptor to duplicate.
2318+
/// * `newfd`: The new file descriptor number to assign to the duplicated file descriptor.
2319+
///
2320+
/// ### Returns
2321+
/// * The new file descriptor on success.
2322+
///
2323+
/// ### Errors
2324+
/// * `EBADF(9)`: If the original file descriptor (`oldfd`) is invalid or the new file descriptor (`newfd`) number is out of range.
2325+
/// ###Panics
2326+
/// * There are no panics for this syscall
2327+
///[dup2(2)](https://linux.die.net/man/2/dup2)
2328+
22602329
pub fn dup2_syscall(&self, oldfd: i32, newfd: i32) -> i32 {
22612330
//checking if the new fd is out of range
22622331
if newfd >= MAXFD || newfd < 0 {
@@ -2272,7 +2341,10 @@ impl Cage {
22722341
} //if the file descriptors are equal, return the new one
22732342

22742343
// get the filedesc_enum
2275-
let checkedfd = self.get_filedescriptor(oldfd).unwrap();
2344+
let checkedfd = match self.get_filedescriptor(oldfd) {
2345+
Ok(fd) => fd,
2346+
Err(_) => return syscall_error(Errno::EBADF, "dup2", "Invalid old file descriptor."),
2347+
};
22762348
let filedesc_enum = checkedfd.write();
22772349
let filedesc_enum = if let Some(f) = &*filedesc_enum {
22782350
f
@@ -2284,6 +2356,41 @@ impl Cage {
22842356
return Self::_dup2_helper(&self, filedesc_enum, newfd, true);
22852357
}
22862358

2359+
/// ## `_dup2_helper`
2360+
///
2361+
/// ### Description
2362+
/// This helper function performs the actual file descriptor duplication process for both `dup` and `dup2` system calls.
2363+
/// It handles the allocation of a new file descriptor, updates the file descriptor table, and increments the reference count of the
2364+
/// associated file object.
2365+
/// * Duplication from `dup2_syscall`: If `fromdup2` is true, the function first closes the existing file descriptor
2366+
/// at `newfd` (if any) before allocating a new file descriptor and updating the file descriptor table.
2367+
/// * Duplication from `dup_syscall`: If `fromdup2` is false, the function allocates a new file descriptor, finds the
2368+
/// first available file descriptor number starting from `newfd`, and updates the file descriptor table.
2369+
/// * Reference Counting: The function increments the reference count of the file object associated with the original file
2370+
/// descriptor. This ensures that the file object is not deleted until all its associated file descriptors are closed.
2371+
/// * Socket Handling: For domain sockets, the function increments the reference count of both the send and receive pipes
2372+
/// associated with the socket.
2373+
/// * Stream Handling: Streams are not currently supported for duplication
2374+
/// * Unhandled File Types: If the file descriptor is associated with a file type that is not handled by the function (i.e.,
2375+
/// not a File, Pipe, Socket, or Stream), the function returns an error (`EACCES`).
2376+
/// * The function does not handle streams.
2377+
/// * Socket Handling: If the file descriptor is associated with a socket, the function handles domain sockets differently
2378+
/// by incrementing the reference count of both the send and receive pipes.
2379+
/// ### Function Arguments
2380+
/// * `self`: A reference to the `FsCalls` struct, which contains the file descriptor table and other system-related data.
2381+
/// * `filedesc_enum`: A reference to the `FileDescriptor` object representing the file descriptor to be duplicated.
2382+
/// * `newfd`: The new file descriptor number to assign to the duplicated file descriptor.
2383+
/// * `fromdup2`: A boolean flag indicating whether the call is from `dup2_syscall` (true) or `dup_syscall` (false).
2384+
///
2385+
/// ### Returns
2386+
/// * The new file descriptor on success.
2387+
///
2388+
/// ### Errors
2389+
/// * `ENFILE(23)`: If there are no available file descriptors.
2390+
/// * `EACCES(13)`: If the file descriptor cannot be duplicated.
2391+
/// ###Panics
2392+
/// * If the file descriptor is associated with a socket, and the inode does not match the file descriptor.
2393+
22872394
pub fn _dup2_helper(&self, filedesc_enum: &FileDescriptor, newfd: i32, fromdup2: bool) -> i32 {
22882395
let (dupfd, mut dupfdguard) = if fromdup2 {
22892396
let mut fdguard = self.filedescriptortable[newfd as usize].write();
@@ -2301,6 +2408,8 @@ impl Cage {
23012408
} else {
23022409
let (newdupfd, guardopt) = self.get_next_fd(Some(newfd));
23032410
if newdupfd < 0 {
2411+
// The function allocates a new file descriptor and updates the file descriptor table,
2412+
// handling the potential for file descriptor table overflow (resulting in an `ENFILE` error).
23042413
return syscall_error(
23052414
Errno::ENFILE,
23062415
"dup2_helper",
@@ -2319,6 +2428,8 @@ impl Cage {
23192428
//incrementing the ref count so that when close is executed on the dup'd file
23202429
//the original file does not get a negative ref count
23212430
match *inodeobj {
2431+
// increments the reference count of the file object associated with the original file descriptor
2432+
// to ensure that the file object is not deleted until all its associated file descriptors are closed.
23222433
Inode::File(ref mut normalfile_inode_obj) => {
23232434
normalfile_inode_obj.refcount += 1;
23242435
}

src/tests/fs_tests.rs

Lines changed: 131 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -495,6 +495,46 @@ pub mod fs_tests {
495495
assert_eq!(cage.exit_syscall(EXIT_SUCCESS), EXIT_SUCCESS);
496496
lindrustfinalize();
497497
}
498+
#[test]
499+
fn ut_lind_fs_dup_invalid_fd() {
500+
let _thelock = setup::lock_and_init();
501+
let cage = interface::cagetable_getref(1);
502+
503+
// Open a file and get a valid file descriptor
504+
let fd = cage.open_syscall("/testfile", O_CREAT | O_WRONLY, S_IRWXA);
505+
assert_ne!(fd, -(Errno::ENOENT as i32));
506+
507+
// Close the file descriptor, making it invalid
508+
assert_eq!(cage.close_syscall(fd), 0);
509+
510+
// Attempt to duplicate the invalid file descriptor
511+
let new_fd = cage.dup_syscall(fd, None);
512+
assert_eq!(new_fd, -(Errno::EBADF as i32));
513+
514+
assert_eq!(cage.exit_syscall(EXIT_SUCCESS), EXIT_SUCCESS);
515+
lindrustfinalize();
516+
}
517+
518+
#[test]
519+
fn ut_lind_fs_dup_full_table() {
520+
let _thelock = setup::lock_and_init();
521+
let cage = interface::cagetable_getref(1);
522+
523+
// Open a large number of files to fill the file descriptor table
524+
for i in 0..1024 {
525+
let fd = cage.open_syscall(&format!("/testfile{}", i), O_CREAT | O_WRONLY, S_IRWXA);
526+
assert_ne!(fd, -(Errno::ENOENT as i32));
527+
}
528+
529+
// Attempt to duplicate a file descriptor, which should fail
530+
let fd = cage.open_syscall("/testfile", O_CREAT | O_WRONLY, S_IRWXA);
531+
assert_ne!(fd, -(Errno::ENOENT as i32));
532+
let new_fd = cage.dup_syscall(fd, None);
533+
assert_eq!(new_fd, -(Errno::EBADF as i32));
534+
535+
assert_eq!(cage.exit_syscall(EXIT_SUCCESS), EXIT_SUCCESS);
536+
lindrustfinalize();
537+
}
498538

499539
#[test]
500540
pub fn ut_lind_fs_dup2() {
@@ -548,6 +588,97 @@ pub mod fs_tests {
548588
lindrustfinalize();
549589
}
550590

591+
#[test]
592+
fn ut_lind_fs_dup2_invalid_fd() {
593+
let _thelock = setup::lock_and_init();
594+
let cage = interface::cagetable_getref(1);
595+
596+
// Open a file
597+
let fd = cage.open_syscall("/testfile", O_CREAT | O_WRONLY, S_IRWXA);
598+
assert_ne!(fd, -(Errno::ENOENT as i32));
599+
600+
// Close the file descriptor, making it invalid
601+
assert_eq!(cage.close_syscall(fd), 0);
602+
603+
// Attempt to duplicate the invalid file descriptor
604+
let new_fd = cage.dup2_syscall(fd, 5);
605+
assert_eq!(new_fd, -(Errno::EBADF as i32));
606+
607+
assert_eq!(cage.exit_syscall(EXIT_SUCCESS), EXIT_SUCCESS);
608+
lindrustfinalize();
609+
}
610+
611+
#[test]
612+
fn ut_lind_fs_dup2_full_table() {
613+
let _thelock = setup::lock_and_init();
614+
let cage = interface::cagetable_getref(1);
615+
616+
// Open a large number of files to fill the file descriptor table
617+
for i in 0..1024 {
618+
let fd = cage.open_syscall(&format!("/testfile{}", i), O_CREAT | O_WRONLY, S_IRWXA);
619+
assert_ne!(fd, -(Errno::ENOENT as i32));
620+
}
621+
622+
// Attempt to duplicate a file descriptor, which should fail
623+
let fd = cage.open_syscall("/testfile", O_CREAT | O_WRONLY, S_IRWXA);
624+
assert_ne!(fd, -(Errno::ENOENT as i32));
625+
let new_fd = cage.dup2_syscall(fd, 5); // Try to duplicate to an existing fd
626+
assert_eq!(new_fd, -(Errno::EBADF as i32));
627+
628+
assert_eq!(cage.exit_syscall(EXIT_SUCCESS), EXIT_SUCCESS);
629+
lindrustfinalize();
630+
}
631+
632+
#[test]
633+
fn ut_lind_fs_dup2_with_fork() {
634+
// Acquiring a lock on TESTMUTEX prevents other tests from running concurrently, and also performs clean env setup.
635+
let _thelock = setup::lock_and_init();
636+
637+
let cage = interface::cagetable_getref(1);
638+
639+
let flags: i32 = O_CREAT | O_RDWR;
640+
let filepath1 = "/dup2file_with_fork1";
641+
let filepath2 = "/dup2file_with_fork2";
642+
643+
// Open file descriptors
644+
let fd1 = cage.open_syscall(filepath1, flags, S_IRWXA);
645+
let fd2 = cage.open_syscall(filepath2, flags, S_IRWXA);
646+
assert!(fd1 >= 0);
647+
assert!(fd2 >= 0);
648+
649+
// Write data to the first file
650+
assert_eq!(cage.write_syscall(fd1, str2cbuf("parent data"), 11), 11);
651+
652+
// Fork the process
653+
assert_eq!(cage.fork_syscall(2), 0);
654+
655+
let child = std::thread::spawn(move || {
656+
let cage2 = interface::cagetable_getref(2);
657+
658+
// In the child process, duplicate fd1 to fd2
659+
assert!(cage2.dup2_syscall(fd1, fd2) >= 0);
660+
661+
// Write new data to the duplicated file descriptor
662+
assert_eq!(cage2.write_syscall(fd2, str2cbuf(" child data"), 11), 11);
663+
664+
assert_eq!(cage2.close_syscall(fd2), 0);
665+
666+
assert_eq!(cage2.exit_syscall(EXIT_SUCCESS), EXIT_SUCCESS);
667+
});
668+
669+
child.join().unwrap();
670+
671+
let mut buffer = sizecbuf(22);
672+
assert_eq!(cage.lseek_syscall(fd1, 0, SEEK_SET), 0);
673+
assert_eq!(cage.read_syscall(fd1, buffer.as_mut_ptr(), 22), 22);
674+
assert_eq!(cbuf2str(&buffer), "parent data child data");
675+
676+
assert_eq!(cage.close_syscall(fd1), 0);
677+
678+
assert_eq!(cage.exit_syscall(EXIT_SUCCESS), EXIT_SUCCESS);
679+
lindrustfinalize();
680+
}
681+
551682
#[test]
552683
pub fn ut_lind_fs_fcntl_valid_args() {
553684
//acquiring a lock on TESTMUTEX prevents other tests from running concurrently,

0 commit comments

Comments
 (0)