diff --git a/src/safeposix/syscalls/net_calls.rs b/src/safeposix/syscalls/net_calls.rs index 678a2451b..d38fb112c 100644 --- a/src/safeposix/syscalls/net_calls.rs +++ b/src/safeposix/syscalls/net_calls.rs @@ -2971,9 +2971,7 @@ impl Cage { } pub fn _epoll_object_allocator(&self) -> i32 { - //seems to only be called in functions that don't have a filedesctable lock, so - // not passing the lock. - + // create a Epoll file descriptor let epollobjfd = Epoll(EpollDesc { mode: 0000, registered_fds: interface::RustHashMap::::new(), @@ -2981,7 +2979,7 @@ impl Cage { errno: 0, flags: 0, }); - //get a file descriptor + // get a file descriptor let (fd, guardopt) = self.get_next_fd(None); if fd < 0 { return fd; @@ -2992,6 +2990,29 @@ impl Cage { return fd; } + /// ## ------------------EPOLL_CREATE SYSCALL------------------ + /// ### Description + /// epoll_create_syscall creates a new epoll instance: it waits for + /// one of the file descriptors from the sets to become ready to perform + /// I/O. + + /// ### Function Arguments + /// The `epoll_create_syscall()` receives one argument: + /// * `size` - the size argument is a legacy argument in Linux and is + /// ignored, but must be greater than zero + + /// ### Returns + /// On success, the system calls return a file descriptor (a nonnegative + /// integer). + /// + /// ### Errors + /// * ENFILE - file descriptor number reached the limit + /// * EINVAL - size is not positive. + /// + /// ### Panics + /// No panic is expected from this syscall + /// + /// more details at https://man7.org/linux/man-pages/man2/epoll_create.2.html pub fn epoll_create_syscall(&self, size: i32) -> i32 { if size <= 0 { return syscall_error( @@ -3003,25 +3024,115 @@ impl Cage { return Self::_epoll_object_allocator(self); } - //this one can still be optimized + /// ## ------------------EPOLL_CTL SYSCALL------------------ + /// ### Description + /// This system call is used to add, modify, or remove entries in the + /// interest list of the epoll instance referred to by the file + /// descriptor epfd. It requests the operation op to be performed for the + /// target file descriptor, fd. + + /// ### Function Arguments + /// The `epoll_ctl_syscall()` receives four arguments: + /// * `epfd` - the epoll file descriptor to be applied the action + /// * `op` - the operation to be performed, valid values for the op argument + /// are: + /// 1. EPOLL_CTL_ADD: Add an entry to the interest list of the epoll file + /// descriptor, epfd. The entry includes the file descriptor, fd, a + /// reference to the corresponding open file description, and the + /// settings specified in event. + /// 2. EPOLL_CTL_MOD: Change the settings associated with fd in the interest + /// list to the new settings specified in event. + /// 3. EPOLL_CTL_DEL: Remove (deregister) the target file descriptor fd from + /// the interest list. + /// * `fd` - the target file descriptor to be performed by op + /// * `event` - The event argument describes the object linked to the file + /// descriptor fd. + + /// ### Returns + /// When successful, epoll_ctl_syscall returns zero. + /// + /// ### Errors + /// * EBADF - epfd or fd is not a valid file descriptor. + /// * EEXIST - op was EPOLL_CTL_ADD, and the supplied file descriptor fd is + /// already registered with this epoll instance. + /// * EINVAL - epfd is not an epoll file descriptor, or fd is the same as + /// epfd, or the requested operation op is not supported by this + /// interface. + /// * ENOENT - op was EPOLL_CTL_MOD or EPOLL_CTL_DEL, and fd is not + /// registered with this epoll instance. + /// * EPERM - The target file fd does not support epoll. This error can + /// occur if fd refers to, for example, a regular file or a directory. + /// + /// ### Panics + /// No panic is expected from this syscall + /// + /// more details at https://man7.org/linux/man-pages/man2/epoll_ctl.2.html pub fn epoll_ctl_syscall(&self, epfd: i32, op: i32, fd: i32, event: &EpollEvent) -> i32 { - //making sure that the epfd is really an epoll fd + // first check the fds are within the valid range + if epfd < 0 || epfd >= MAXFD { + return syscall_error( + Errno::EBADF, + "epoll ctl", + "provided epoll fd is not a valid file descriptor", + ); + } + + if fd < 0 || fd >= MAXFD { + return syscall_error( + Errno::EBADF, + "epoll ctl", + "provided fd is not a valid file descriptor", + ); + } + + // making sure that the epfd is really an epoll fd let checkedfd = self.get_filedescriptor(epfd).unwrap(); let mut unlocked_fd = checkedfd.write(); if let Some(filedesc_enum_epollfd) = &mut *unlocked_fd { if let Epoll(epollfdobj) = filedesc_enum_epollfd { - //check if the other fd is an epoll or not... + // first check if fd equals to epfd + // standard says EINVAL should be returned when fd equals to epfd + // must check before trying to get the read lock of fd + // otherwise deadlock would occur (trying to get read lock while the + // same fd is already hold with write lock) + if fd == epfd { + return syscall_error( + Errno::EINVAL, + "epoll ctl", + "provided fd is the same as epfd", + ); + } + + // check if the other fd is an epoll or not... let checkedfd = self.get_filedescriptor(fd).unwrap(); let unlocked_fd = checkedfd.read(); if let Some(filedesc_enum) = &*unlocked_fd { - if let Epoll(_) = filedesc_enum { - return syscall_error( - Errno::EBADF, - "epoll ctl", - "provided fd is not a valid file descriptor", - ); + match filedesc_enum { + Epoll(_) => { + // nested Epoll (i.e. Epoll monitoring on Epoll file descriptor) + // is allowed on Linux with some restrictions, though we currently do + // not support this + + return syscall_error( + Errno::EBADF, + "epoll ctl", + "provided fd is not a valid file descriptor", + ); + } + File(_) => { + // according to standard, EPERM should be returned when + // fd refers to a file or directory + return syscall_error( + Errno::EPERM, + "epoll ctl", + "The target file fd does not support epoll.", + ); + } + // other file descriptors are valid + _ => {} } } else { + // fd is not a valid file descriptor return syscall_error( Errno::EBADF, "epoll ctl", @@ -3029,15 +3140,22 @@ impl Cage { ); } - //now that we know that the types are all good... + // now that we know that the types are all good... match op { EPOLL_CTL_DEL => { - //since remove returns the value at the key and the values will always be - // EpollEvents, I am using this to optimize the code - epollfdobj.registered_fds.remove(&fd).unwrap().1; + // check if the fd that we are modifying exists or not + if !epollfdobj.registered_fds.contains_key(&fd) { + return syscall_error( + Errno::ENOENT, + "epoll ctl", + "fd is not registered with this epfd", + ); + } + // if the fd already exists, remove the entry + epollfdobj.registered_fds.remove(&fd); } EPOLL_CTL_MOD => { - //check if the fd that we are modifying exists or not + // check if the fd that we are modifying exists or not if !epollfdobj.registered_fds.contains_key(&fd) { return syscall_error( Errno::ENOENT, @@ -3045,7 +3163,7 @@ impl Cage { "fd is not registered with this epfd", ); } - //if the fd already exists, insert overwrites the prev entry + // if the fd already exists, insert overwrites the prev entry epollfdobj.registered_fds.insert( fd, EpollEvent { @@ -3055,6 +3173,7 @@ impl Cage { ); } EPOLL_CTL_ADD => { + //check if the fd that we are modifying exists or not if epollfdobj.registered_fds.contains_key(&fd) { return syscall_error( Errno::EEXIST, @@ -3062,6 +3181,7 @@ impl Cage { "fd is already registered", ); } + // add the fd and events epollfdobj.registered_fds.insert( fd, EpollEvent { @@ -3075,22 +3195,63 @@ impl Cage { } } } else { + // epfd is not epoll object return syscall_error( - Errno::EBADF, + Errno::EINVAL, "epoll ctl", - "provided fd is not a valid file descriptor", + "provided epoll fd is not a valid epoll file descriptor", ); } } else { + // epfd is not a valid file descriptor return syscall_error( Errno::EBADF, "epoll ctl", - "provided epoll fd is not a valid epoll file descriptor", + "provided fd is not a valid file descriptor", ); } return 0; } + /// ## ------------------EPOLL_WAIT SYSCALL------------------ + /// ### Description + /// The epoll_wait_syscall waits for events on the epoll instance + /// referred to by the file descriptor epfd. The buffer pointed to by events + /// is used to return information from the ready list about file descriptors + /// in the interest list that have some events available. Up to maxevents + /// are returned by epoll_wait_syscall(). The maxevents argument must be + /// greater than zero. + + /// ### Function Arguments + /// The `epoll_wait_syscall()` receives four arguments: + /// * `epfd` - the epoll file descriptor on which the action is to be + /// performed + /// * `events` - The buffer of array of EpollEvent used to store returned + /// information from the ready list about file descriptors in the interest + /// list that have some events available + /// * `maxevents` - maximum number of returned events. The maxevents + /// argument must be greater than zero. + /// * `timeout` - The timeout argument is a RustDuration structure that + /// specifies the interval that epoll_wait_syscall should block waiting + /// for a file descriptor to become ready. + + /// ### Returns + /// On success, epoll_wait_syscall returns the number of file descriptors + /// ready for the requested I/O operation, or zero if no file descriptor + /// became ready when timeout expires + /// + /// ### Errors + /// * EBADF - epfd is not a valid file descriptor. + /// * EINTR - The call was interrupted by a signal handler before either (1) + /// any of the requested events occurred or (2) the timeout expired + /// * EINVAL - epfd is not an epoll file descriptor, or maxevents is less + /// than or equal to zero. + /// + /// ### Panics + /// * when maxevents is larger than the size of events, index_out_of_bounds + /// panic may occur + /// + /// more details at https://man7.org/linux/man-pages/man2/epoll_wait.2.html pub fn epoll_wait_syscall( &self, epfd: i32, @@ -3098,20 +3259,44 @@ impl Cage { maxevents: i32, timeout: Option, ) -> i32 { + // current implementation of epoll is still based on poll_syscall, + // we are essentially transforming the epoll input to poll input then + // feeding into poll_syscall, and transforming the poll_syscall output + // back to epoll result. Such method gives several issues: + // 1. epoll is supposed to support a brand new mode called edge-triggered + // mode, which only considers a fd to be ready only when new changes are made + // to the fd. Currently, we do not support this feature + // 2. several flags, such as EPOLLRDHUP, EPOLLERR, etc. are not supported + // since poll_syscall currently does not support these flags, so epoll_syscall + // that relies on poll_syscall, as a consequence, does not support them + + // first check the fds are within the valid range + if epfd < 0 || epfd >= MAXFD { + return syscall_error( + Errno::EBADF, + "epoll wait", + "provided epoll fd is not a valid file descriptor", + ); + } + + // get the file descriptor object let checkedfd = self.get_filedescriptor(epfd).unwrap(); let mut unlocked_fd = checkedfd.write(); if let Some(filedesc_enum) = &mut *unlocked_fd { + // check if epfd is a valid Epoll object if let Epoll(epollfdobj) = filedesc_enum { - if maxevents < 0 { + // maxevents should be larger than 0 + if maxevents <= 0 { return syscall_error( Errno::EINVAL, "epoll wait", "max events argument is not a positive number", ); } + // transform epoll instance into poll instance let mut poll_fds_vec: Vec = vec![]; let mut rm_fds_vec: Vec = vec![]; - let mut num_events: usize = 0; + // iterate through each registered fds for set in epollfdobj.registered_fds.iter() { let (&key, &value) = set.pair(); @@ -3123,62 +3308,85 @@ impl Cage { continue; } + // get the events to monitor let events = value.events; let mut structpoll = PollStruct { fd: key, events: 0, revents: 0, }; + // check for each supported event + // EPOLLIN: if the fd is ready to read if events & EPOLLIN as u32 > 0 { structpoll.events |= POLLIN; } + // EPOLLOUT: if the fd is ready to write if events & EPOLLOUT as u32 > 0 { structpoll.events |= POLLOUT; } - if events & EPOLLERR as u32 > 0 { - structpoll.events |= POLLERR; + // EPOLLPRI: if the fd has any exception? + if events & EPOLLPRI as u32 > 0 { + structpoll.events |= POLLPRI; } + // now PollStruct is constructed, push it to the vector poll_fds_vec.push(structpoll); - num_events += 1; } for fd in rm_fds_vec.iter() { epollfdobj.registered_fds.remove(fd); } // remove closed fds + // call poll_syscall let poll_fds_slice = &mut poll_fds_vec[..]; let pollret = Self::poll_syscall(&self, poll_fds_slice, timeout); if pollret < 0 { + // in case of error, return the error return pollret; } + // the counter is used for making sure the number of returned ready fds + // is smaller than or equal to maxevents let mut count = 0; - let end_idx: usize = interface::rust_min(num_events, maxevents as usize); - for result in poll_fds_slice[..end_idx].iter() { + for result in poll_fds_slice.iter() { + // transform the poll result into epoll result + // poll_event is used for marking if the fd is ready for something + + // events are requested events for poll + // revents are the returned events and all results are stored here let mut poll_event = false; let mut event = EpollEvent { events: 0, fd: epollfdobj.registered_fds.get(&result.fd).unwrap().fd, }; + // check for POLLIN if result.revents & POLLIN > 0 { event.events |= EPOLLIN as u32; poll_event = true; } + // check for POLLOUT if result.revents & POLLOUT > 0 { event.events |= EPOLLOUT as u32; poll_event = true; } - if result.revents & POLLERR > 0 { - event.events |= EPOLLERR as u32; + // check for POLLPRI + if result.revents & POLLPRI > 0 { + event.events |= EPOLLPRI as u32; poll_event = true; } + // if the fd is ready for something + // add it to the return array if poll_event { events[count] = event; count += 1; + // if already reached maxevents, break + if count >= maxevents as usize { + break; + } } } return count as i32; } else { + // the fd is not an epoll object return syscall_error( Errno::EINVAL, "epoll wait", @@ -3186,6 +3394,7 @@ impl Cage { ); } } else { + // epfd is not a valid file descriptor return syscall_error( Errno::EBADF, "epoll wait", diff --git a/src/tests/networking_tests.rs b/src/tests/networking_tests.rs index f56e3bf1d..e47482fc0 100644 --- a/src/tests/networking_tests.rs +++ b/src/tests/networking_tests.rs @@ -2998,26 +2998,613 @@ pub mod net_tests { lindrustfinalize(); } - /* Creates an epoll instance, registers the server socket and file descriptor with epoll, and then wait for events using - epoll_wait_syscall(). It handles the events based on their types (EPOLLIN or EPOLLOUT) and performs the necessary operations - like accepting new connections, sending/receiving data, and modifying the event flags */ #[test] + pub fn ut_lind_net_epoll_create_bad_input() { + // this test is used for testing epoll_create_syscall with error/edge cases + // specifically + // following tests are performed: + // 1. test for errno with invalid size argument + + // 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 for invalid size argument + assert_eq!(cage.epoll_create_syscall(0), -(Errno::EINVAL as i32)); + + assert_eq!(cage.exit_syscall(EXIT_SUCCESS), EXIT_SUCCESS); + lindrustfinalize(); + } + + #[test] + pub fn ut_lind_net_epoll_ctl_bad_input() { + // this test is used for testing epoll_ctl_syscall with error/edge cases + // specifically + // following tests are performed: + // 1. test for errno with invalid fd number + // 2. test for errno with invalid epfd number + // 3. test for errno with out of range fd number + // 4. test for errno with out of range epfd number + // 5. test for errno when epfd is not epoll instance + // 6. test for errno when epfd and fd are the same + // 7. test for errno when fd is a file fd + // 8. test for errno when trying to modify a fd that does not added to set + // 9. test for errno when trying to delete a fd that does not added to set + // 10. test for errno when trying to add a fd that already added to the set + // 11. test for errno when passing invalid flag + + // 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 instance + let epfd = cage.epoll_create_syscall(1); + + // create a pipe fd + let mut pipefds = PipeArray { + readfd: -1, + writefd: -1, + }; + assert_eq!(cage.pipe2_syscall(&mut pipefds, O_NONBLOCK), 0); + + // create a file fd + let filefd = cage.open_syscall("/netepolltest.txt", O_CREAT | O_EXCL | O_RDWR, S_IRWXA); + assert!(filefd > 0); + + // test for unexist fd number + assert_eq!( + cage.epoll_ctl_syscall( + epfd, + EPOLL_CTL_MOD, + 10, + &mut EpollEvent { + events: EPOLLIN as u32, + fd: 10, + } + ), + -(Errno::EBADF as i32) + ); + + assert_eq!( + cage.epoll_ctl_syscall( + 10, + EPOLL_CTL_MOD, + pipefds.readfd, + &mut EpollEvent { + events: EPOLLIN as u32, + fd: pipefds.readfd, + } + ), + -(Errno::EBADF as i32) + ); + + // test for out of range fd number + assert_eq!( + cage.epoll_ctl_syscall( + epfd, + EPOLL_CTL_MOD, + -1, + &mut EpollEvent { + events: EPOLLIN as u32, + fd: -1, + } + ), + -(Errno::EBADF as i32) + ); + assert_eq!( + cage.epoll_ctl_syscall( + -1, + EPOLL_CTL_MOD, + pipefds.readfd, + &mut EpollEvent { + events: EPOLLIN as u32, + fd: pipefds.readfd, + } + ), + -(Errno::EBADF as i32) + ); + + // test for fd that is not epoll fd + assert_eq!( + cage.epoll_ctl_syscall( + filefd, + EPOLL_CTL_ADD, + pipefds.readfd, + &mut EpollEvent { + events: EPOLLIN as u32, + fd: pipefds.readfd, + } + ), + -(Errno::EINVAL as i32) + ); + + // test when fd and epfd are the same + assert_eq!( + cage.epoll_ctl_syscall( + epfd, + EPOLL_CTL_ADD, + epfd, + &mut EpollEvent { + events: EPOLLIN as u32, + fd: epfd, + } + ), + -(Errno::EINVAL as i32) + ); + + // test when fd is a file fd + assert_eq!( + cage.epoll_ctl_syscall( + epfd, + EPOLL_CTL_ADD, + filefd, + &mut EpollEvent { + events: EPOLLIN as u32, + fd: filefd, + } + ), + -(Errno::EPERM as i32) + ); + + // test for modifying fd that does not exists + assert_eq!( + cage.epoll_ctl_syscall( + epfd, + EPOLL_CTL_MOD, + pipefds.readfd, + &mut EpollEvent { + events: EPOLLIN as u32, + fd: pipefds.readfd, + } + ), + -(Errno::ENOENT as i32) + ); + + // test for deleting fd that does not exists + assert_eq!( + cage.epoll_ctl_syscall( + epfd, + EPOLL_CTL_DEL, + pipefds.readfd, + &mut EpollEvent { + events: EPOLLIN as u32, + fd: pipefds.readfd, + } + ), + -(Errno::ENOENT as i32) + ); + + // now add a fd + assert_eq!( + cage.epoll_ctl_syscall( + epfd, + EPOLL_CTL_ADD, + pipefds.readfd, + &mut EpollEvent { + events: EPOLLIN as u32, + fd: pipefds.readfd, + } + ), + 0 + ); + + // test for adding fd that already exists + assert_eq!( + cage.epoll_ctl_syscall( + epfd, + EPOLL_CTL_ADD, + pipefds.readfd, + &mut EpollEvent { + events: EPOLLIN as u32, + fd: pipefds.readfd, + } + ), + -(Errno::EEXIST as i32) + ); + + // test for passing invalid flag + assert_eq!( + cage.epoll_ctl_syscall( + epfd, + 123, + pipefds.readfd, + &mut EpollEvent { + events: EPOLLIN as u32, + fd: pipefds.readfd, + } + ), + -(Errno::EINVAL as i32) + ); + + assert_eq!(cage.exit_syscall(EXIT_SUCCESS), EXIT_SUCCESS); + lindrustfinalize(); + } + + #[test] + pub fn ut_lind_net_epoll_wait_bad_input() { + // this test is used for testing epoll_wait_syscall with error/edge cases + // specifically + // following tests are performed: + // 1. test for errno with out of range fd number + // 2. test for errno with invalid fd number + // 3. test for errno when fd is not an epoll instance + // 4. test for errno with invalid maxevents argument + + // 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 instance + let epfd = cage.epoll_create_syscall(1); + + // create a pipe fd + let mut pipefds = PipeArray { + readfd: -1, + writefd: -1, + }; + assert_eq!(cage.pipe2_syscall(&mut pipefds, O_NONBLOCK), 0); + + // create a file fd + let filefd = cage.open_syscall("/netepolltest.txt", O_CREAT | O_EXCL | O_RDWR, S_IRWXA); + assert!(filefd > 0); + + let mut event_list: Vec = vec![ + EpollEvent { + events: EPOLLIN as u32, + fd: 0, + }; + 2 + ]; + + // test for out of range fd range + assert_eq!( + cage.epoll_wait_syscall(-1, &mut event_list, 1, None), + -(Errno::EBADF as i32) + ); + + // test for invalid fd range + assert_eq!( + cage.epoll_wait_syscall(10, &mut event_list, 1, None), + -(Errno::EBADF as i32) + ); + + // test for fd that is not epoll + assert_eq!( + cage.epoll_wait_syscall(filefd, &mut event_list, 1, None), + -(Errno::EINVAL as i32) + ); + + // test for invalid maxevents argument + assert_eq!( + cage.epoll_wait_syscall(epfd, &mut event_list, 0, None), + -(Errno::EINVAL as i32) + ); + + assert_eq!(cage.exit_syscall(EXIT_SUCCESS), EXIT_SUCCESS); + lindrustfinalize(); + } + + #[test] + pub fn ut_lind_net_epoll_maxevents_arg() { + // this test is used for testing maxevents argument of epoll_wait_syscall + // specifically + + // 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); + + let pipe_num = 10; + // create some pipes + let mut pipefds = vec![ + PipeArray { + readfd: -1, + writefd: -1, + }; + pipe_num + ]; + for pipefd in pipefds.iter_mut() { + assert_eq!(cage.pipe2_syscall(pipefd, O_NONBLOCK), 0); + } + + // create an epoll instance + let epfd = cage.epoll_create_syscall(1); + // add all pipes to epoll + for pipefd in pipefds.iter_mut() { + assert_eq!( + cage.epoll_ctl_syscall( + epfd, + EPOLL_CTL_ADD, + pipefd.readfd, + &mut EpollEvent { + events: EPOLLIN as u32, + fd: pipefd.readfd, + } + ), + 0 + ); + + // write something to the pipe at the same time + assert_eq!(cage.write_syscall(pipefd.writefd, str2cbuf("test"), 4), 4); + } + + // at this point, all pipes are added to epoll, and they should all be readable + + // prepare the event_list to store the return value + let mut event_list: Vec = vec![EpollEvent { events: 0, fd: 0 }; pipe_num]; + + // test #1: all the fds should be ready + assert_eq!( + cage.epoll_wait_syscall(epfd, &mut event_list, pipe_num as i32, None), + pipe_num as i32 + ); + for event in event_list.iter() { + // check if all fd are marked as readable + assert_ne!(event.events & (EPOLLIN as u32), 0); + } + + // test #2: maxevents set to be smaller than pipe_num + + // clear event_list + let mut event_list: Vec = vec![ + EpollEvent { + events: 0xdeadbeef, + fd: 0, + }; + pipe_num + ]; + + assert_eq!(cage.epoll_wait_syscall(epfd, &mut event_list, 5, None), 5); + + for i in 0..pipe_num { + // even though all fds are ready, only first 5 should be marked with EPOLLIN + if i < 5 { + assert_ne!(event_list[i].events & (EPOLLIN as u32), 0); + } else { + assert_eq!(event_list[i].events, 0xdeadbeef); + } + } + + // test #3: maxevents set to be larger than actual ready fds (case 1) + + // clear event_list + let mut event_list: Vec = vec![ + EpollEvent { + events: 0xdeadbeef, + fd: 0, + }; + pipe_num + ]; + // first let's consume some pipes + let mut buf = sizecbuf(4); + assert_eq!(cage.read_syscall(pipefds[0].readfd, buf.as_mut_ptr(), 4), 4); + assert_eq!(cbuf2str(&buf), "test"); + assert_eq!(cage.read_syscall(pipefds[1].readfd, buf.as_mut_ptr(), 4), 4); + assert_eq!(cbuf2str(&buf), "test"); + // now pipe with index 0 and 1 are consumed and they are no longer readable + assert_eq!( + cage.epoll_wait_syscall(epfd, &mut event_list, pipe_num as i32, None), + (pipe_num - 2) as i32 + ); + + for i in 0..pipe_num { + // even though all fds are ready, only first 5 should be marked with EPOLLIN + if i < 8 { + assert_ne!(event_list[i].events & (EPOLLIN as u32), 0); + } else { + assert_eq!(event_list[i].events, 0xdeadbeef); + } + } + + // test #4: maxevents set to be larger than actual ready fds (case 2) + // clear event_list + let mut event_list: Vec = vec![ + EpollEvent { + events: 0xdeadbeef, + fd: 0, + }; + pipe_num + ]; + // we try to only read 5 this time + // since the number of avaliable fds is still 8 + // so it is supposed to return 5 + assert_eq!(cage.epoll_wait_syscall(epfd, &mut event_list, 5, None), 5); + for i in 0..pipe_num { + // only first 5 should be marked with EPOLLIN and others remain untouched + if i < 5 { + assert_ne!(event_list[i].events & (EPOLLIN as u32), 0); + } else { + assert_eq!(event_list[i].events, 0xdeadbeef); + } + } + + assert_eq!(cage.exit_syscall(EXIT_SUCCESS), EXIT_SUCCESS); + lindrustfinalize(); + } + + #[test] + pub fn ut_lind_net_epoll_timeout() { + // this test is used for testing timeout argument of epoll_wait_syscall + // specifically + // following tests are performed: + // 1. test for epoll_wait when timeout could expire + // 2. test for epoll_wait when not fd is monitored but timeout is set + + // 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); + + // subtest 1: epoll when timeout could expire + // create a TCP AF_UNIX socket + let serversockfd = cage.socket_syscall(AF_INET, SOCK_STREAM, 0); + let clientsockfd = cage.socket_syscall(AF_INET, SOCK_STREAM, 0); + assert!(serversockfd > 0); + assert!(clientsockfd > 0); + + let port: u16 = generate_random_port(); + let sockaddr = interface::SockaddrV4 { + sin_family: AF_INET as u16, + sin_port: port.to_be(), + sin_addr: interface::V4Addr { + s_addr: u32::from_ne_bytes([127, 0, 0, 1]), + }, + padding: 0, + }; + let socket = interface::GenSockaddr::V4(sockaddr); //127.0.0.1 from bytes above + + // server bind and listen + assert_eq!(cage.bind_syscall(serversockfd, &socket), 0); + assert_eq!(cage.listen_syscall(serversockfd, 4), 0); + + assert_eq!(cage.fork_syscall(2), 0); + assert_eq!(cage.close_syscall(clientsockfd), 0); + + // this barrier is used for preventing + // an unfixed bug (`close` could block when other thread/cage is `accept`) from + // deadlocking the test + let barrier = Arc::new(Barrier::new(2)); + let barrier_2 = barrier.clone(); + + // client connects to the server to send and recv data... + let threadclient = interface::helper_thread(move || { + let cage2 = interface::cagetable_getref(2); + assert_eq!(cage2.close_syscall(serversockfd), 0); + + barrier_2.wait(); + + // connect to the server + assert_eq!(cage2.connect_syscall(clientsockfd, &socket), 0); + + // wait for 100ms + interface::sleep(interface::RustDuration::from_millis(100)); + + // send some message to client + assert_eq!(cage2.send_syscall(clientsockfd, str2cbuf("test"), 4, 0), 4); + + assert_eq!(cage2.close_syscall(clientsockfd), 0); + cage2.exit_syscall(EXIT_SUCCESS); + }); + + // make sure client thread closed the duplicated socket before server start to + // accept + barrier.wait(); + + // wait for client to connect + let mut sockgarbage = interface::GenSockaddr::V4(interface::SockaddrV4::default()); + let sockfd = cage.accept_syscall(serversockfd as i32, &mut sockgarbage); + + // add to client socket to epoll + // perform another test by the way: the fd field of EpollEvent is user data by + // standard which means we could put anything we want here, and kernel + // is not supposed to touch it + let epfd = cage.epoll_create_syscall(1); + assert_eq!( + cage.epoll_ctl_syscall( + epfd, + EPOLL_CTL_ADD, + sockfd, + &mut EpollEvent { + events: EPOLLIN as u32, + fd: 123, + } + ), + 0 + ); + // event_list used for holding return value + let mut event_list: Vec = vec![EpollEvent { events: 0, fd: 0 }]; + + // this counter is used for recording how many times do select returns due to + // timeout + let mut counter = 0; + + loop { + let epoll_result = cage.epoll_wait_syscall( + epfd, + &mut event_list, + 1, + Some(interface::RustDuration::new(0, 10000000)), // 10ms + ); + assert!(epoll_result >= 0); + // epoll timeout after 10ms, but client will send messages after 100ms + // so there should be some timeout return + if epoll_result == 0 { + counter += 1; + } else if event_list[0].events & (EPOLLIN as u32) != 0 { + assert_eq!(event_list[0].fd, 123); // fd field should remain touched + // just received the message, check the message and break + let mut buf = sizecbuf(4); + assert_eq!(cage.recv_syscall(sockfd, buf.as_mut_ptr(), 4, 0), 4); + assert_eq!(cbuf2str(&buf), "test"); + break; + } else { + unreachable!(); + } + } + // check if epoll timeout correctly + assert!(counter > 0); + + threadclient.join().unwrap(); + + // subtest 2: epoll when nothing is monitored, `epoll` here should behave like + // `sleep` + let epfd2 = cage.epoll_create_syscall(1); + + let start_time = interface::starttimer(); + let timeout = interface::RustDuration::new(0, 10000000); // 10ms + let epoll_result = cage.epoll_wait_syscall(epfd2, &mut event_list, 1, Some(timeout)); + assert!(epoll_result == 0); + // should wait for at least 10ms + assert!(interface::readtimer(start_time) >= timeout); + + assert_eq!(cage.exit_syscall(EXIT_SUCCESS), EXIT_SUCCESS); + lindrustfinalize(); + } + + #[test] + #[ignore] pub fn ut_lind_net_epoll() { - //acquiring a lock on TESTMUTEX prevents other tests from running concurrently, + // test for epoll monitoring on multiple different file descriptors: + // 1. AF_INET server socket waiting for two clients + // 2. AF_INET server socket's connection file descriptor with clients + // 3. AF_UNIX server socket's connection file descriptor with a client + // 4. 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); - let filefd = cage.open_syscall("/netepolltest.txt", O_CREAT | O_EXCL | O_RDWR, S_IRWXA); - assert!(filefd > 0); - + // creating socket file descriptors let serversockfd = cage.socket_syscall(AF_INET, SOCK_STREAM, 0); let clientsockfd1 = cage.socket_syscall(AF_INET, SOCK_STREAM, 0); let clientsockfd2 = cage.socket_syscall(AF_INET, SOCK_STREAM, 0); + let serversockfd_unix = cage.socket_syscall(AF_UNIX, SOCK_STREAM, 0); + let clientsockfd_unix = cage.socket_syscall(AF_UNIX, SOCK_STREAM, 0); + + assert!(serversockfd > 0); + assert!(clientsockfd1 > 0); + assert!(clientsockfd2 > 0); + assert!(serversockfd_unix > 0); + assert!(clientsockfd_unix > 0); + + // creating a pipe + let mut pipefds = PipeArray { + readfd: -1, + writefd: -1, + }; + assert_eq!(cage.pipe_syscall(&mut pipefds), 0); + + // create a INET address let port: u16 = generate_random_port(); - // Create and set up the file descriptor and sockets let sockaddr = interface::SockaddrV4 { sin_family: AF_INET as u16, sin_port: port.to_be(), @@ -3026,155 +3613,340 @@ pub mod net_tests { }, padding: 0, }; - let socket = interface::GenSockaddr::V4(sockaddr); + let socket = interface::GenSockaddr::V4(sockaddr); //127.0.0.1 from bytes above + + //binding to a socket + let serversockaddr_unix = + interface::new_sockaddr_unix(AF_UNIX as u16, "server_poll".as_bytes()); + let serversocket_unix = interface::GenSockaddr::Unix(serversockaddr_unix); + + let clientsockaddr_unix = + interface::new_sockaddr_unix(AF_UNIX as u16, "client_poll".as_bytes()); + let clientsocket_unix = interface::GenSockaddr::Unix(clientsockaddr_unix); + + assert_eq!(cage.bind_syscall(serversockfd_unix, &serversocket_unix), 0); + assert_eq!(cage.bind_syscall(clientsockfd_unix, &clientsocket_unix), 0); + assert_eq!(cage.listen_syscall(serversockfd_unix, 1), 0); + assert_eq!(cage.bind_syscall(serversockfd, &socket), 0); assert_eq!(cage.listen_syscall(serversockfd, 4), 0); - let mut event_list = vec![ - EpollEvent { + // create epoll file descriptor + let epfd = cage.epoll_create_syscall(1); + assert!(epfd > 0); + + // add file descriptors to epoll + cage.epoll_ctl_syscall( + epfd, + EPOLL_CTL_ADD, + serversockfd, + &mut EpollEvent { events: EPOLLIN as u32, fd: serversockfd, }, - EpollEvent { + ); + + cage.epoll_ctl_syscall( + epfd, + EPOLL_CTL_ADD, + serversockfd_unix, + &mut EpollEvent { events: EPOLLIN as u32, - fd: filefd, + fd: serversockfd_unix, }, - ]; + ); - cage.fork_syscall(2); - // Client 1 connects to the server to send and recv data - let thread1 = interface::helper_thread(move || { - interface::sleep(interface::RustDuration::from_millis(30)); + cage.epoll_ctl_syscall( + epfd, + EPOLL_CTL_ADD, + pipefds.readfd, + &mut EpollEvent { + events: EPOLLIN as u32, + fd: pipefds.readfd, + }, + ); + + assert_eq!(cage.fork_syscall(2), 0); // used for AF_INET thread client 1 + assert_eq!(cage.fork_syscall(3), 0); // used for AF_INET thread client 2 + assert_eq!(cage.fork_syscall(4), 0); // used for AF_UNIX thread client + + assert_eq!(cage.fork_syscall(5), 0); // used for pipe thread + + assert_eq!(cage.close_syscall(clientsockfd1), 0); + assert_eq!(cage.close_syscall(clientsockfd2), 0); + assert_eq!(cage.close_syscall(clientsockfd_unix), 0); + + // this barrier have to ensure that the clients finish the connect before we do + // the epoll due to an unfixed bug (`close` could block when other + // thread/cage is `accept`) + let barrier = Arc::new(Barrier::new(3)); + let barrier_clone1 = barrier.clone(); + let barrier_clone2 = barrier.clone(); + + // this barrier is used for control the flow the pipe + let barrier_pipe = Arc::new(Barrier::new(2)); + let barrier_pipe_clone = barrier_pipe.clone(); + + // due to an unfixed bug in ref counter of AF_UNIX socket pipe + // have to make sure all the threads exits only after the AF_UNIX test finished + let barrier_exit = Arc::new(Barrier::new(4)); + let barrier_exit_clone1 = barrier_exit.clone(); + let barrier_exit_clone2 = barrier_exit.clone(); + let barrier_exit_clone3 = barrier_exit.clone(); + + // client 1 connects to the server to send and recv data + let threadclient1 = interface::helper_thread(move || { let cage2 = interface::cagetable_getref(2); - // Connect to server and send data - assert_eq!(cage2.connect_syscall(clientsockfd1, &socket), 0); - assert_eq!( - cage2.send_syscall(clientsockfd1, str2cbuf(&"test"), 4, 0), - 4 - ); - // Wait for data processing, give it a longer pause time so that it can process - // all of the data received - interface::sleep(interface::RustDuration::from_millis(100)); - // Close the server socket and exit the thread assert_eq!(cage2.close_syscall(serversockfd), 0); + assert_eq!(cage2.close_syscall(clientsockfd2), 0); + + // connect to server + assert_eq!(cage2.connect_syscall(clientsockfd1, &socket), 0); + barrier_clone1.wait(); + + // send message to server + assert_eq!(cage2.send_syscall(clientsockfd1, str2cbuf("test"), 4, 0), 4); + + interface::sleep(interface::RustDuration::from_millis(1)); + + // receive message from server + let mut buf = sizecbuf(4); + assert_eq!(cage2.recv_syscall(clientsockfd1, buf.as_mut_ptr(), 4, 0), 4); + assert_eq!(cbuf2str(&buf), "test"); + + assert_eq!(cage2.close_syscall(clientsockfd1), 0); + barrier_exit_clone1.wait(); cage2.exit_syscall(EXIT_SUCCESS); }); - cage.fork_syscall(3); - // Client 2 connects to the server to send and recv data - let thread2 = interface::helper_thread(move || { - interface::sleep(interface::RustDuration::from_millis(45)); + // client 2 connects to the server to send and recv data + let threadclient2 = interface::helper_thread(move || { let cage3 = interface::cagetable_getref(3); - // Connect to server and send data + assert_eq!(cage3.close_syscall(serversockfd), 0); + assert_eq!(cage3.close_syscall(clientsockfd1), 0); + + // connect to server assert_eq!(cage3.connect_syscall(clientsockfd2, &socket), 0); - assert_eq!( - cage3.send_syscall(clientsockfd2, str2cbuf(&"test"), 4, 0), - 4 - ); + barrier_clone2.wait(); - interface::sleep(interface::RustDuration::from_millis(100)); - // Close the server socket and exit the thread - assert_eq!(cage3.close_syscall(serversockfd), 0); + // send message to server + assert_eq!(cage3.send_syscall(clientsockfd2, str2cbuf("test"), 4, 0), 4); + + interface::sleep(interface::RustDuration::from_millis(1)); + + // receive message from server + let mut buf = sizecbuf(4); + let mut result: i32; + loop { + result = cage3.recv_syscall(clientsockfd2, buf.as_mut_ptr(), 4, 0); + if result != -libc::EINTR { + break; // if the error was EINTR, retry the syscall + } + } + assert_eq!(result, 4); + assert_eq!(cbuf2str(&buf), "test"); + + assert_eq!(cage3.close_syscall(clientsockfd2), 0); + barrier_exit_clone2.wait(); cage3.exit_syscall(EXIT_SUCCESS); }); - // Acting as the server and processing the request - let thread3 = interface::helper_thread(move || { - let epfd = cage.epoll_create_syscall(1); - assert!(epfd > 0); + let threadclient_unix = interface::helper_thread(move || { + let cage4 = interface::cagetable_getref(4); + assert_eq!(cage4.close_syscall(serversockfd_unix), 0); + assert_eq!(cage4.close_syscall(serversockfd), 0); + // connect to server assert_eq!( - cage.epoll_ctl_syscall(epfd, EPOLL_CTL_ADD, serversockfd, &mut event_list[0]), + cage4.connect_syscall(clientsockfd_unix, &serversocket_unix), 0 ); + + // send message to server assert_eq!( - cage.epoll_ctl_syscall(epfd, EPOLL_CTL_ADD, filefd, &mut event_list[1]), - 0 + cage4.send_syscall(clientsockfd_unix, str2cbuf("test"), 4, 0), + 4 ); - // Event processing loop - for _counter in 0..600 { - let num_events = cage.epoll_wait_syscall( - epfd, - &mut event_list, - 1, - Some(interface::RustDuration::ZERO), - ); - assert!(num_events >= 0); - - // Wait for events using epoll_wait_syscall - for event in &mut event_list[..num_events as usize] { - // Check for any activity in the input socket and if there are events ready for - // reading - if event.events & (EPOLLIN as u32) != 0 { - // If the socket returned was listener socket, then there's a new connection - if event.fd == serversockfd { - // Handle new connections - let port: u16 = generate_random_port(); - let sockaddr = interface::SockaddrV4 { - sin_family: AF_INET as u16, - sin_port: port.to_be(), - sin_addr: interface::V4Addr { - s_addr: u32::from_ne_bytes([127, 0, 0, 1]), - }, - padding: 0, - }; - let mut addr = interface::GenSockaddr::V4(sockaddr); // 127.0.0.1 from bytes above - let newsockfd = cage.accept_syscall(serversockfd, &mut addr); - let event = interface::EpollEvent { - events: EPOLLIN as u32, - fd: newsockfd, - }; - // Error raised to indicate that the socket file descriptor couldn't be - // added to the epoll instance + + interface::sleep(interface::RustDuration::from_millis(1)); + + // recieve message from server + let mut buf = sizecbuf(4); + let mut result: i32; + loop { + result = cage4.recv_syscall(clientsockfd_unix, buf.as_mut_ptr(), 4, 0); + if result != -libc::EINTR { + break; // if the error was EINTR, retry the syscall + } + } + assert_eq!(result, 4); + assert_eq!(cbuf2str(&buf), "test"); + + assert_eq!(cage4.close_syscall(clientsockfd_unix), 0); + cage4.exit_syscall(EXIT_SUCCESS); + }); + + let thread_pipe = interface::helper_thread(move || { + let cage5 = interface::cagetable_getref(5); + + interface::sleep(interface::RustDuration::from_millis(1)); + // send message to pipe + assert_eq!(cage5.write_syscall(pipefds.writefd, str2cbuf("test"), 4), 4); + + let mut buf = sizecbuf(5); + // wait until peer read the message + barrier_pipe_clone.wait(); + + // read the message sent by peer + assert_eq!(cage5.read_syscall(pipefds.readfd, buf.as_mut_ptr(), 5), 5); + assert_eq!(cbuf2str(&buf), "test2"); + + barrier_exit_clone3.wait(); + cage5.exit_syscall(EXIT_SUCCESS); + }); + + barrier.wait(); + + let event_list_size = 5; + let mut event_list: Vec = + vec![EpollEvent { events: 0, fd: 0 }; event_list_size]; + // acting as the server and processing the request + // Server loop to handle connections and I/O + // Check for any activity in any of the Input sockets + for _counter in 0..600 { + // epoll call + let num_events = cage.epoll_wait_syscall( + epfd, + &mut event_list, + event_list_size as i32, + Some(interface::RustDuration::ZERO), + ); + assert!(num_events >= 0); // check for error + + for event in &mut event_list[..num_events as usize] { + // Check for any activity in the input socket and if there are events ready for + // reading + if event.events & (EPOLLIN as u32) != 0 { + // If the socket returned was listener socket, then there's a new connection + if event.fd == serversockfd { + let mut sockgarbage = + interface::GenSockaddr::V4(interface::SockaddrV4::default()); + let sockfd = cage.accept_syscall(event.fd as i32, &mut sockgarbage); + assert!(sockfd > 0); + let event = interface::EpollEvent { + events: EPOLLIN as u32 | EPOLLOUT as u32, + fd: sockfd, + }; + // Error raised to indicate that the socket file descriptor couldn't be + // added to the epoll instance + assert_eq!( + cage.epoll_ctl_syscall(epfd, EPOLL_CTL_ADD, sockfd, &event), + 0 + ); + } else if event.fd == serversockfd_unix { + // unix socket + let mut sockgarbage = interface::GenSockaddr::Unix( + interface::new_sockaddr_unix(AF_UNIX as u16, "".as_bytes()), + ); + let sockfd = cage.accept_syscall(event.fd as i32, &mut sockgarbage); + assert!(sockfd > 0); + let event = interface::EpollEvent { + events: EPOLLIN as u32 | EPOLLOUT as u32, + fd: sockfd, + }; + // Error raised to indicate that the socket file descriptor couldn't be + // added to the epoll instance + assert_eq!( + cage.epoll_ctl_syscall(epfd, EPOLL_CTL_ADD, sockfd, &event), + 0 + ); + } else if event.fd == pipefds.readfd { + // pipe + let mut buf = sizecbuf(4); + // read the message from peer + assert_eq!(cage.read_syscall(pipefds.readfd, buf.as_mut_ptr(), 4), 4); + assert_eq!(cbuf2str(&buf), "test"); + + // write the message from peer + assert_eq!( + cage.write_syscall(pipefds.writefd, str2cbuf("test2"), 5) as usize, + 5 + ); + barrier_pipe.wait(); + + // pipe epoll test done + assert_eq!( + cage.epoll_ctl_syscall( + epfd, + EPOLL_CTL_DEL, + event.fd, + &EpollEvent { events: 0, fd: 0 } + ), + 0 + ); + } else { + //If the socket is in established conn., then we recv the data. If there's + // no data, then close the client socket. + let mut buf = sizecbuf(4); + let mut recvresult: i32; + loop { + // receive message from peer + recvresult = cage.recv_syscall(event.fd as i32, buf.as_mut_ptr(), 4, 0); + if recvresult != -libc::EINTR { + break; // if the error was EINTR, retry the + // syscall + } + } + if recvresult == 4 { + if cbuf2str(&buf) == "test" { + continue; + } + } else if recvresult == -libc::ECONNRESET { + // peer closed the connection + assert_eq!(cage.close_syscall(event.fd as i32), 0); assert_eq!( - cage.epoll_ctl_syscall(epfd, EPOLL_CTL_ADD, newsockfd, &event), + cage.epoll_ctl_syscall( + epfd, + EPOLL_CTL_DEL, + event.fd, + &EpollEvent { events: 0, fd: 0 } + ), 0 ); - } else if event.fd == filefd { - // Handle writing to the file - // Update - assert_eq!(cage.write_syscall(filefd, str2cbuf("test"), 4), 4); - assert_eq!(cage.lseek_syscall(filefd, 0, SEEK_SET), 0); - event.events = EPOLLOUT as u32; - } else { - // Handle receiving data from established connections - let mut buf = sizecbuf(4); - let recres = cage.recv_syscall(event.fd, buf.as_mut_ptr(), 4, 0); - assert_eq!(recres & !4, 0); - if recres == 4 { - assert_eq!(cbuf2str(&buf), "test"); - event.events = EPOLLOUT as u32; - } else { - assert_eq!(cage.close_syscall(event.fd), 0); - } - } - } - - if event.events & (EPOLLOUT as u32) != 0 { - // Check if there are events ready for writing - if event.fd == filefd { - // Handle reading from the file - let mut read_buf1 = sizecbuf(4); - assert_eq!(cage.read_syscall(filefd, read_buf1.as_mut_ptr(), 4), 4); - assert_eq!(cbuf2str(&read_buf1), "test"); - } else { - // Handle sending data over connections - assert_eq!(cage.send_syscall(event.fd, str2cbuf(&"test"), 4, 0), 4); - event.events = EPOLLIN as u32; } } } + if event.events & (EPOLLOUT as u32) != 0 { + // Data is sent out this socket, it's no longer ready for writing + assert_eq!( + cage.send_syscall(event.fd as i32, str2cbuf("test"), 4, 0), + 4 + ); + // remove the fd + assert_eq!( + cage.epoll_ctl_syscall( + epfd, + EPOLL_CTL_DEL, + event.fd, + &EpollEvent { events: 0, fd: 0 } + ), + 0 + ); + } } + } + assert_eq!(cage.close_syscall(serversockfd), 0); + assert_eq!(cage.close_syscall(serversockfd_unix), 0); - // Close the server socket and exit the thread - assert_eq!(cage.close_syscall(serversockfd), 0); - assert_eq!(cage.exit_syscall(EXIT_SUCCESS), EXIT_SUCCESS); - }); + // let threads exit + barrier_exit.wait(); - thread1.join().unwrap(); - thread2.join().unwrap(); - thread3.join().unwrap(); + threadclient1.join().unwrap(); + threadclient2.join().unwrap(); + threadclient_unix.join().unwrap(); + thread_pipe.join().unwrap(); + assert_eq!(cage.exit_syscall(EXIT_SUCCESS), EXIT_SUCCESS); lindrustfinalize(); }