diff --git a/src/safeposix/syscalls/fs_calls.rs b/src/safeposix/syscalls/fs_calls.rs index b0ba36e7..bfd04ebc 100644 --- a/src/safeposix/syscalls/fs_calls.rs +++ b/src/safeposix/syscalls/fs_calls.rs @@ -2443,31 +2443,123 @@ impl Cage { } } - //------------------------------------LSEEK SYSCALL------------------------------------ + /// ## -------------------------------- LSEEK SYSCALL ------------------------- + /// ### Description + /// + /// The `lseek_syscall()` repositions the offset of the file associated + /// with the file descriptor `fd` to a new location as specified by + /// `offset` and `whence`. The new offset is calculated relative to the + /// position specified by whence. This syscall allows the file offset to + /// be set beyond the end of the file (but this does not change the size + /// of the file). If data is later written at this point, subsequent reads + /// of the data in the gap (a "hole") return null bytes ('\0') until data + /// is actually written into the gap. + /// + /// ### Function Arguments + /// + /// The `lseek_syscall()` receives three arguments: + /// * `fd` - It is the file descriptor, which identifies the file being + /// accessed + /// * `offset` - It determines the position in the file where the file + /// offset should be set. It specifies the number of bytes from a + /// particular reference point. + /// * `whence` - It determines how the seek operation is performed. There + /// are three possible values for whence: `SEEK_SET`: The file offset is + /// set to 0 + offset bytes. `SEEK_CUR`: The file offset is set to its + /// current location plus offset bytes. `SEEK_END`: The file offset is set + /// to the size of the file plus offset bytes. + /// + /// ### Returns + /// + /// Upon successful completion of this call, we return the resulting offset + /// location as measured in bytes from the beginning of the file. + /// Otherwise, errors or panics are returned for different scenarios. + /// + /// ### Errors + /// + /// * `EINVAL` - The `whence` type is different from the available values; + /// seek to a position before the start of the file; seek to after last + /// position in directory + /// * `ESPIPE` - Seek is not possible when file descriptor is a + /// socket/pipe/stream/epoll. + /// + /// + /// ### Panics + /// + /// * When there is some issue fetching the file descriptor. + /// * When the file type file descriptor contains a Socket as an inode. + /// + /// For more detailed description of all the commands and return values, see + /// [lseek(2)](https://man7.org/linux/man-pages/man2/lseek.2.html) pub fn lseek_syscall(&self, fd: i32, offset: isize, whence: i32) -> i32 { + //BUG + //If the provided file descriptor is out of bounds, get_filedescriptor returns + //Err(), unwrapping on which produces a 'panic!' + //otherwise, file descriptor table entry is stored in 'checkedfd' let checkedfd = self.get_filedescriptor(fd).unwrap(); + // Acquire a write lock on the file descriptor to ensure exclusive access. let mut unlocked_fd = checkedfd.write(); + + // Check if the file descriptor object is valid if let Some(filedesc_enum) = &mut *unlocked_fd { //confirm fd type is seekable match filedesc_enum { + // Return an error for Sockets, as they do not support the concept of seeking to a + // specific offset because data arrives in a continuous stream from the network. + Socket(_) => syscall_error( + Errno::ESPIPE, + "lseek", + "file descriptor is associated with a socket, cannot seek", + ), + // Return an error for Streams, as like sockets, streams are sequential and do not + // support seeking to an offset. + Stream(_) => syscall_error( + Errno::ESPIPE, + "lseek", + "file descriptor is associated with a stream, cannot seek", + ), + // Return an error for Pipes, as they are designed for sequential reads and writes + // between processes. Seeking within a pipe would not be possible. + Pipe(_) => syscall_error( + Errno::ESPIPE, + "lseek", + "file descriptor is associated with a pipe, cannot seek", + ), + // Return an error for Epoll, as Epoll file descriptors are for event notification + // and do not hold any data themselves, so seeking is not possible. + Epoll(_) => syscall_error( + Errno::ESPIPE, + "lseek", + "file descriptor is associated with an epollfd, cannot seek", + ), File(ref mut normalfile_filedesc_obj) => { + // Get the inode object from the inode table associated with the file + // descriptor. let inodeobj = FS_METADATA .inodetable .get(&normalfile_filedesc_obj.inode) .unwrap(); - //handle files/directories differently + // Match the type of inode object with the type (File, Socket, CharDev, Dir) + // Handle files/directories differently match &*inodeobj { Inode::File(normalfile_inode_obj) => { + // For file type inode, match the type of whence first, and based + // on that, update the final position of the offset of the file. let eventualpos = match whence { + // Simply update the offset of the file to the given value. SEEK_SET => offset, + // Get the current offset position of the file and add `offset`. SEEK_CUR => normalfile_filedesc_obj.position as isize + offset, + // Get the file size and add `offset` position. SEEK_END => normalfile_inode_obj.size as isize + offset, _ => { return syscall_error(Errno::EINVAL, "lseek", "unknown whence"); } }; + // Sanity check to make sure that the final position of seeking + // doesn't go before position 0 in the file. if eventualpos < 0 { return syscall_error( Errno::EINVAL, @@ -2475,30 +2567,42 @@ impl Cage { "seek to before position 0 in file", ); } - //subsequent writes to the end of the file must zero pad up until this - // point if we overran the end of our file - // when seeking + // subsequent writes to the end of the file must zero pad up (filling + // the gap between the current end of the file and the new position with + // zero bytes ('\0')) until this point if we overran the end of our file + // when seeking. This is currently supported in write and pwrite + // syscalls. + // Update the final position of the file descriptor object normalfile_filedesc_obj.position = eventualpos as usize; - //return the location that we sought to + // Return the location that we sought to eventualpos as i32 } Inode::CharDev(_) => { - 0 //for character files, rather than seeking, we + 0 // for character files, rather than seeking, we // transparently do nothing } + // A Sanity check is added to make sure that there is no such case when the + // fd type is "File" and the inode type is "Socket". This state is ideally + // not possible, so we panic in such cases. Inode::Socket(_) => { panic!("lseek: socket fd and inode don't match types") } Inode::Dir(dir_inode_obj) => { - //for directories we seek between entries, and thus our end position is - // the total number of entries + // For directory type inode, we seek between directory entries, + // and based on the whence check, we update the final position. let eventualpos = match whence { + // Simply update the offset of the directory to the given value. SEEK_SET => offset, + // Get the current position of the directory pointing to a directory + // entry and add the offset to it. SEEK_CUR => normalfile_filedesc_obj.position as isize + offset, + // The Inode dictionary contains the directory entries mapped with + // the inode object, and final position is updated by adding the + // total count of the entries with the given offset. SEEK_END => { dir_inode_obj.filename_to_inode_dict.len() as isize + offset } @@ -2507,7 +2611,8 @@ impl Cage { } }; - //confirm that the location we want to seek to is valid + // Sanity check to make sure that the final position of seeking + // doesn't go before position 0 in the directory. if eventualpos < 0 { return syscall_error( Errno::EINVAL, @@ -2515,6 +2620,8 @@ impl Cage { "seek to before position 0 in directory", ); } + // Return an error if the position we are seeking is after the + // last possible position in the directory. if eventualpos > dir_inode_obj.filename_to_inode_dict.len() as isize { return syscall_error( Errno::EINVAL, @@ -2523,32 +2630,13 @@ impl Cage { ); } + // Update the final position of the file descriptor object normalfile_filedesc_obj.position = eventualpos as usize; - //return the location that we sought to + // Return the location that we sought to eventualpos as i32 } } } - Socket(_) => syscall_error( - Errno::ESPIPE, - "lseek", - "file descriptor is associated with a socket, cannot seek", - ), - Stream(_) => syscall_error( - Errno::ESPIPE, - "lseek", - "file descriptor is associated with a stream, cannot seek", - ), - Pipe(_) => syscall_error( - Errno::ESPIPE, - "lseek", - "file descriptor is associated with a pipe, cannot seek", - ), - Epoll(_) => syscall_error( - Errno::ESPIPE, - "lseek", - "file descriptor is associated with an epollfd, cannot seek", - ), } } else { syscall_error(Errno::EBADF, "lseek", "invalid file descriptor") @@ -3058,58 +3146,136 @@ impl Cage { return dupfd; } - //------------------------------------CLOSE SYSCALL------------------------------------ - + /// ## ------------------CLOSE SYSCALL------------------ + /// ### Description + /// + /// The `close_syscall()` is used to close a file descriptor, so that it no + /// longer refers to any file and may be reused. This is an essential + /// operation for managing resources, as file descriptors are a limited + /// resource in any operating system. + /// + /// ### Function Arguments + /// + /// The `close_syscall()` receives one argument: + /// * `fd` - This argument refers to the file descriptor to be closed. + /// + /// ### Returns + /// + /// Upon successful completion of this call, it returns 0. Otherwise, it + /// returns error number set with the appropriate error code. + /// + /// ### Errors + /// + /// * The syscall returns errors which occur in its helper functions. + /// + /// ### Panics + /// + /// * This syscall indirectly panics when the helper function panics. + /// + /// For more detailed description of all the commands and return values, see + /// [close(2)](https://man7.org/linux/man-pages/man2/close.2.html) pub fn close_syscall(&self, fd: i32) -> i32 { - //check that the fd is valid + // Call the helper function which validates the result and deletes + // reference of the file descriptor. return Self::_close_helper(self, fd); } + /// ## ----------------- CLOSE HELPER INNER ------------------ + /// ### Description + /// + /// The `_close_helper_inner` function manages the cleanup and closing of + /// various types of file descriptors (files, directories, pipes, sockets) + /// by decrementing reference counts and removing inodes if necessary. + /// ### Function Arguments + /// + /// The function receives one argument: + /// * `fd` - This argument refers to the file descriptor to be closed. + /// + /// ### Returns + /// + /// Upon successful completion of this call, it returns 0. Otherwise, it + /// returns errors set with the appropriate error code. + /// + /// ### Errors + /// + /// * EBADF - The given file descriptor is invalid + /// * ENOEXEC - The Non-regular type file (dir/chardev) in file object table + /// + /// ### Panics + /// + /// * If the provided file descriptor is out of bounds, causing unwrap() to + /// panic + /// * When the file type filedescriptor contains a Socket as an inode. + /// + /// For more detailed description of all the commands and return values, see + /// [close(2)](https://man7.org/linux/man-pages/man2/close.2.html) pub fn _close_helper_inner(&self, fd: i32) -> i32 { + //BUG + //if the provided file descriptor is out of bounds, get_filedescriptor returns + // Err(), unwrapping on which produces a 'panic!' + //otherwise, file descriptor table entry is stored in 'checkedfd' let checkedfd = self.get_filedescriptor(fd).unwrap(); let mut unlocked_fd = checkedfd.write(); if let Some(filedesc_enum) = &mut *unlocked_fd { - //Decide how to proceed depending on the fd type. - //First we check in the file descriptor to handle sockets (no-op), sockets - // (clean the socket), and pipes (clean the pipe), and if it is a - // normal file descriptor we decrement the refcount to reflect + // We decide, how to proceed depending on the fd type. + // First we check in the file descriptor to handle stream / epoll (no-op), + // sockets (clean the socket), and pipes (clean the pipe), and if it is a + // normal file descriptor we decrement the reference count to reflect // one less reference to the file. match filedesc_enum { //if we are a socket, we dont change disk metadata - Stream(_) => {} - Epoll(_) => {} //Epoll closing not implemented yet + Stream(_) => {} // Streams don't require any additional cleanup + Epoll(_) => {} // TODO: Epoll closing not implemented yet Socket(ref mut socket_filedesc_obj) => { + // Retrieve the socket file descriptor object and get the write + // lock on the socket handle. let sock_tmp = socket_filedesc_obj.handle.clone(); let mut sockhandle = sock_tmp.write(); - // we need to do the following if UDS - if let Some(ref mut ui) = sockhandle.unix_info { - let inodenum = ui.inode; - if let Some(sendpipe) = ui.sendpipe.as_ref() { - sendpipe.decr_ref(O_WRONLY); - //last reference, lets remove it - if sendpipe.is_pipe_closed() { - ui.sendpipe = None; + // we need to do the following if UDS (Unix Domain Socket) + // AF_UNIX represents the Unix domain sockets. + let socket_type = sockhandle.domain; + if socket_type == AF_UNIX { + if let Some(ref mut ui) = sockhandle.unix_info { + let inodenum = ui.inode; + // Decrement the reference count for the send pipe if it exists. If the + // reference count drops to zero, remove the send pipe. + if let Some(sendpipe) = ui.sendpipe.as_ref() { + sendpipe.decr_ref(O_WRONLY); + //last reference, lets remove it + if sendpipe.is_pipe_closed() { + ui.sendpipe = None; + } } - } - if let Some(receivepipe) = ui.receivepipe.as_ref() { - receivepipe.decr_ref(O_RDONLY); - //last reference, lets remove it - if receivepipe.is_pipe_closed() { - ui.receivepipe = None; + // Decrement the reference count for the receive pipe if it exists. If + // the reference count drops to zero, remove + // the receive pipe. + if let Some(receivepipe) = ui.receivepipe.as_ref() { + receivepipe.decr_ref(O_RDONLY); + //last reference, lets remove it + if receivepipe.is_pipe_closed() { + ui.receivepipe = None; + } } - } - let mut inodeobj = FS_METADATA.inodetable.get_mut(&ui.inode).unwrap(); - if let Inode::Socket(ref mut sock) = *inodeobj { - sock.refcount -= 1; - if sock.refcount == 0 { - if sock.linkcount == 0 { + // Retrieve the inode object for the socket and decrement its reference + // count. If both the reference count and + // link count are zero, the socket is no longer needed. + let mut inodeobj = FS_METADATA.inodetable.get_mut(&ui.inode).unwrap(); + if let Inode::Socket(ref mut sock) = *inodeobj { + sock.refcount -= 1; + if sock.refcount == 0 && sock.linkcount == 0 { + // Remove the socket from the inode table and the domain + // socket paths if it is no longer needed. drop(inodeobj); + // Get the entire path of the socket which is used for removing + // it from the metadata file. let path = normpath( convpath(sockhandle.localaddr.unwrap().path()), self, ); + // Remove the reference of the inode from the inodetable FS_METADATA.inodetable.remove(&inodenum); + // Remove any domain socket paths associated with the socket NET_METADATA.domsock_paths.remove(&path); } } @@ -3117,11 +3283,12 @@ impl Cage { } } Pipe(ref pipe_filedesc_obj) => { - // lets decrease the pipe objects internal ref count for the corresponding end - // depending on what flags are set + // Decrease the pipe objects internal ref count for the corresponding end + // depending on what flags are set (O_RDONLY or O_WRONLY). pipe_filedesc_obj.pipe.decr_ref(pipe_filedesc_obj.flags); } File(ref normalfile_filedesc_obj) => { + // Retrieve the inode object for the file. let inodenum = normalfile_filedesc_obj.inode; let mut inodeobj = FS_METADATA.inodetable.get_mut(&inodenum).unwrap(); @@ -3129,20 +3296,31 @@ impl Cage { Inode::File(ref mut normalfile_inode_obj) => { normalfile_inode_obj.refcount -= 1; - //if it's not a reg file, then we have nothing to close - //Inode::File is a regular file by default + // Check if it's not a regular file, then we have nothing to close + // Inode::File is a regular file by default + // Reference count represents the number of active file descriptors + // pointing to the file. if normalfile_inode_obj.refcount == 0 { + // FileObjectTable stores the entries of the currently opened files + // in the system, so if there are no + // active file descriptors pointing to the + // file, we delete them from the table. FILEOBJECTTABLE .remove(&inodenum) .unwrap() .1 .close() .unwrap(); + // Link count as 0 represents that there are no hard links present + // for the file, so we need to remove it from the filesystem. if normalfile_inode_obj.linkcount == 0 { drop(inodeobj); - //removing the file from the entire filesystem (interface, + // removing the file from the entire filesystem (interface, // metadata, and object table) FS_METADATA.inodetable.remove(&inodenum); + // FILEDATAPREFIX represents the common prefix of the name + // of the file which combined with the inode number represents + // a unique entity. It stores the data of the inode object. let sysfilename = format!("{}{}", FILEDATAPREFIX, inodenum); interface::removefile(sysfilename).unwrap(); log_metadata(&FS_METADATA, inodenum); @@ -3154,17 +3332,24 @@ impl Cage { Inode::Dir(ref mut dir_inode_obj) => { dir_inode_obj.refcount -= 1; - //if it's not a reg file, then we have nothing to close + // File object table only contains references for the regular + // type files, and since "Directory" is not a regular file type, + // it should not exist in the table. Return an error if the + // directory exists in the file object table. match FILEOBJECTTABLE.get(&inodenum) { Some(_) => { return syscall_error( Errno::ENOEXEC, "close or dup", - "Non-regular file in file object table", + "Non-regular file (dir) in file object table", ); } + // Continue if the object table doesn't contain the inode. None => {} } + // When the link count is 2 and the reference count is 0, it means + // the directory is empty and no processes are using it. This allows + // the directory to be safely removed from the file system. if dir_inode_obj.linkcount == 2 && dir_inode_obj.refcount == 0 { //The reference to the inode has to be dropped to avoid //deadlocking because the remove() method will need to @@ -3180,16 +3365,18 @@ impl Cage { } Inode::CharDev(ref mut char_inode_obj) => { char_inode_obj.refcount -= 1; - - //if it's not a reg file, then we have nothing to close + // Since "CharDev" is not a regular file type, it should not + // exist in the file object table. Return an error if the + // chardev inode exists in the file object table. match FILEOBJECTTABLE.get(&inodenum) { Some(_) => { return syscall_error( Errno::ENOEXEC, "close or dup", - "Non-regular file in file object table", + "Non-regular file (chardev) in file object table", ); } + // Continue if the object table doesn't contain the inode. None => {} } if char_inode_obj.linkcount == 0 && char_inode_obj.refcount == 0 { @@ -3201,27 +3388,72 @@ impl Cage { } log_metadata(&FS_METADATA, inodenum); } + // A Sanity check is added to make sure that there is no such case when the + // fd type is "File" and the inode type is "Socket". This state is ideally + // not possible, so we panic in such cases. Inode::Socket(_) => { panic!("close(): Socket inode found on a filedesc fd.") } } } } + // If everything is successful, we return 0 0 } else { return syscall_error(Errno::EBADF, "close", "invalid file descriptor"); } } + /// ## ------------------CLOSE HELPER ------------------ + /// ### Description + /// + /// The `_close_helper` function calls `_close_helper_inner` to handle + /// the specifics of closing the file descriptor (like decrementing + /// reference counts, handling special cases for sockets, pipes, etc.). + /// Once `_close_helper_inner` has successfully done its job (indicated + /// by returning a non-negative value), `_close_helper` removes the file + /// descriptor from the file descriptor table. This ensures that the file + /// descriptor is fully closed and no longer tracked by the system. + /// + /// ### Function Arguments + /// + /// The `_close_helper()` receives one argument: + /// * `fd` - This argument refers to the file descriptor to be closed. + /// + /// ### Returns + /// + /// Upon successful completion of this call, it returns 0. Otherwise, it + /// returns an error number with the appropriate error code. + /// + /// ### Errors + /// + /// * There are no explicit errors returned by this function. The errors + /// thrown by `_close_helper_inner` function are returned. + /// + /// ### Panics + /// + /// * If the provided file descriptor is out of bounds, causing unwrap() to + /// panic. pub fn _close_helper(&self, fd: i32) -> i32 { let inner_result = self._close_helper_inner(fd); + // Return value of less than 0 indicates that there was some error + // closing the fd, so we return the error. if inner_result < 0 { return inner_result; } - //removing descriptor from fd table + // Once we know that the closing of the fd was successful, + // we remove the file descriptor from the fd table and free the space. + // + // Bug: + // if the provided file descriptor is out of bounds, get_filedescriptor returns + // Err(), unwrapping on which produces a 'panic!' + //otherwise, file descriptor table entry is stored in 'checkedfd' let checkedfd = self.get_filedescriptor(fd).unwrap(); let mut unlocked_fd = checkedfd.write(); + // This operation effectively removes the file descriptor from the fd table + // by removing its reference from the variable and then not using it further, + // allowing it to be dropped if unlocked_fd.is_some() { let _discarded_fd = unlocked_fd.take(); } diff --git a/src/tests/fs_tests.rs b/src/tests/fs_tests.rs index 193bcf0a..ed99bf4a 100644 --- a/src/tests/fs_tests.rs +++ b/src/tests/fs_tests.rs @@ -4066,4 +4066,332 @@ pub mod fs_tests { lindrustfinalize(); } + + #[test] + pub fn ut_lind_fs_lseek_on_file() { + // 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); + + // Test to create a file and check if seeking to a new location is possible. + let fd = cage.open_syscall("/test_file", O_CREAT | O_WRONLY, S_IRWXA); + assert!(fd >= 0); + + // Attempt to seek within the file and check if it succeeds + assert_eq!(cage.lseek_syscall(fd, 10, SEEK_SET), 10); + + assert_eq!(cage.exit_syscall(EXIT_SUCCESS), EXIT_SUCCESS); + lindrustfinalize(); + } + + #[test] + pub fn ut_lind_fs_lseek_on_directory() { + // 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); + + // Create a directory and try to seek within it. + let path = "/test_dir"; + assert_eq!(cage.mkdir_syscall(path, S_IRWXA), 0); + let fd = cage.open_syscall(path, O_RDONLY, S_IRWXA); + assert!(fd >= 0); + + // Attempt to seek within the directory and check if it succeeds + assert_eq!(cage.lseek_syscall(fd, 1, SEEK_SET), 1); + + assert_eq!(cage.exit_syscall(EXIT_SUCCESS), EXIT_SUCCESS); + lindrustfinalize(); + } + + #[test] + pub fn ut_lind_fs_lseek_invalid_whence() { + // 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); + + // Test to create a file and check for invalid `whence` value + let fd = cage.open_syscall("/test_file", O_CREAT | O_RDWR, S_IRWXA); + assert!(fd >= 0); + + // Attempt to seek with an invalid `whence` value and check if it returns an + // error + assert_eq!( + cage.lseek_syscall(fd, 10, 999), // Invalid whence value + -(Errno::EINVAL as i32) + ); + + assert_eq!(cage.exit_syscall(EXIT_SUCCESS), EXIT_SUCCESS); + lindrustfinalize(); + } + + #[test] + pub fn ut_lind_fs_lseek_beyond_file_size() { + // 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); + + // Test to create a file and seek beyond its size + let fd = cage.open_syscall("/test_file", O_CREAT | O_RDWR, S_IRWXA); + assert!(fd >= 0); + + // Write sample data to the file. + assert_eq!(cage.write_syscall(fd, str2cbuf("hello"), 5), 5); + + // Seek beyond the end of the file and verify if it succeeds + assert_eq!( + cage.lseek_syscall(fd, 10, SEEK_END), + 15 // 5 (file size) + 10 (offset) + ); + + assert_eq!(cage.exit_syscall(EXIT_SUCCESS), EXIT_SUCCESS); + lindrustfinalize(); + } + + #[test] + pub fn ut_lind_fs_lseek_before_start_of_file() { + // 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); + + // Test to create a file and attempt to seek before the start of the file + let fd = cage.open_syscall("/test_file", O_CREAT | O_RDWR, S_IRWXA); + assert!(fd >= 0); + + // Attempt to seek to a negative offset and check if it returns an error + // using "SEEK_SET" whence, where we are explicitly setting the file + // offset to -10 value. + assert_eq!( + cage.lseek_syscall(fd, -10, SEEK_SET), + -(Errno::EINVAL as i32) + ); + + // Attempt to seek to a negative offset and check if it returns an error + // using "SEEK_CUR" whence, where current position of the file is 0, + // as it's empty initially, and we are adding -10 to the offset. + assert_eq!( + cage.lseek_syscall(fd, -10, SEEK_CUR), + -(Errno::EINVAL as i32) + ); + + assert_eq!(cage.exit_syscall(EXIT_SUCCESS), EXIT_SUCCESS); + lindrustfinalize(); + } + + #[test] + pub fn ut_lind_fs_lseek_on_pipe() { + // 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); + + // Create a pipe and attempt to seek within it + let mut pipe_fds = PipeArray::default(); + assert_eq!(cage.pipe_syscall(&mut pipe_fds), 0); + let read_fd = pipe_fds.readfd; + + // Attempt to seek within the pipe and check if it returns an error + assert_eq!( + cage.lseek_syscall(read_fd, 10, SEEK_SET), + -(Errno::ESPIPE as i32) + ); + + assert_eq!(cage.exit_syscall(EXIT_SUCCESS), EXIT_SUCCESS); + lindrustfinalize(); + } + + #[test] + pub fn ut_lind_fs_lseek_on_chardev() { + // 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); + + // Attempt to seek within a character device file + let path = "/dev/null"; + let fd = cage.open_syscall(path, O_RDWR, S_IRWXA); + + // Seek within the character device and check if it returns 0 (no operation) + assert_eq!(cage.lseek_syscall(fd, 10, SEEK_SET), 0); + + assert_eq!(cage.exit_syscall(EXIT_SUCCESS), EXIT_SUCCESS); + lindrustfinalize(); + } + + #[test] + pub fn ut_lind_fs_lseek_on_epoll() { + // 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); + + // Create an Epoll and try to seek from it. + let epfd = cage.epoll_create_syscall(1); + assert!(epfd > 0); + + // Attempt to seek from the epoll and check if it returns an error + assert_eq!( + cage.lseek_syscall(epfd, 10, SEEK_SET), + -(Errno::ESPIPE as i32) + ); + + assert_eq!(cage.exit_syscall(EXIT_SUCCESS), EXIT_SUCCESS); + lindrustfinalize(); + } + + #[test] + pub fn ut_lind_fs_close_regular_file() { + // 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); + + // Create and open a regular file, then close it. + let fd = cage.open_syscall("/test_file", O_CREAT | O_RDWR, S_IRWXA); + assert!(fd >= 0); + + // Write sample data to the file. + assert_eq!(cage.write_syscall(fd, str2cbuf("hello"), 5), 5); + + // Close the file descriptor, which should succeed. + assert_eq!(cage.close_syscall(fd), 0); + + // Attempt to close the file descriptor again to ensure it's already closed. + // Expect an error for "Invalid File Descriptor". + assert_eq!(cage.close_syscall(fd), -(Errno::EBADF as i32)); + + assert_eq!(cage.exit_syscall(EXIT_SUCCESS), EXIT_SUCCESS); + lindrustfinalize(); + } + + #[test] + pub fn ut_lind_fs_close_directory() { + // 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); + + // Create a directory and open it. + let path = "/test_dir"; + assert_eq!(cage.mkdir_syscall(path, S_IRWXA), 0); + let fd = cage.open_syscall(path, O_RDONLY, S_IRWXA); + assert!(fd >= 0); + + // Close the directory file descriptor, which should succeed. + assert_eq!(cage.close_syscall(fd), 0); + + // Attempt to close the file descriptor again to ensure it's already closed. + // Expect an error for "Invalid File Descriptor". + assert_eq!(cage.close_syscall(fd), -(Errno::EBADF as i32)); + + assert_eq!(cage.exit_syscall(EXIT_SUCCESS), EXIT_SUCCESS); + lindrustfinalize(); + } + + #[test] + pub fn ut_lind_fs_close_socket() { + // 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); + + // Create a socket pair. + let mut socketpair = interface::SockPair::default(); + assert_eq!( + Cage::socketpair_syscall(cage.clone(), AF_UNIX, SOCK_STREAM, 0, &mut socketpair), + 0 + ); + + // Close both the socket file descriptors, which should succeed. + assert_eq!(cage.close_syscall(socketpair.sock1), 0); + assert_eq!(cage.close_syscall(socketpair.sock2), 0); + + // Attempt to close the file descriptors again to ensure they are already + // closed. Expect an error for "Invalid File Descriptor". + assert_eq!(cage.close_syscall(socketpair.sock1), -(Errno::EBADF as i32)); + assert_eq!(cage.close_syscall(socketpair.sock2), -(Errno::EBADF as i32)); + + assert_eq!(cage.exit_syscall(EXIT_SUCCESS), EXIT_SUCCESS); + lindrustfinalize(); + } + + #[test] + pub fn ut_lind_fs_close_pipe() { + // 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); + + // Create a pipe. + let mut pipe_fds = PipeArray::default(); + assert_eq!(cage.pipe_syscall(&mut pipe_fds), 0); + let read_fd = pipe_fds.readfd; + let write_fd = pipe_fds.writefd; + + // Write data to the pipe + let write_data = "Testing"; + assert_eq!( + cage.write_syscall(write_fd, write_data.as_ptr(), write_data.len()), + write_data.len() as i32 + ); + + // Read the data from the pipe. + let mut buf = sizecbuf(7); + assert_eq!( + cage.read_syscall(read_fd, buf.as_mut_ptr(), buf.len()), + write_data.len() as i32 + ); + assert_eq!(cbuf2str(&buf), write_data); + + // Close the pipe file descriptors, which should succeed. + assert_eq!(cage.close_syscall(read_fd), 0); + assert_eq!(cage.close_syscall(write_fd), 0); + + // Attempt to close the file descriptor again to ensure they are already closed. + // Expect an error for "Invalid File Descriptor". + assert_eq!(cage.close_syscall(read_fd), -(Errno::EBADF as i32)); + assert_eq!(cage.close_syscall(write_fd), -(Errno::EBADF as i32)); + + assert_eq!(cage.exit_syscall(EXIT_SUCCESS), EXIT_SUCCESS); + lindrustfinalize(); + } + + #[test] + pub fn ut_lind_fs_close_chardev() { + // 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); + + // Open a character device file. + let fd = cage.open_syscall("/dev/zero", O_RDWR, S_IRWXA); + assert!(fd >= 0); + + // Close the character device file descriptor, which should succeed. + assert_eq!(cage.close_syscall(fd), 0); + + // Attempt to close the file descriptor again to ensure it's already closed. + // Expect an error for "Invalid File Descriptor". + assert_eq!(cage.close_syscall(fd), -(Errno::EBADF as i32)); + + assert_eq!(cage.exit_syscall(EXIT_SUCCESS), EXIT_SUCCESS); + lindrustfinalize(); + } }