// Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved.1// SPDX-License-Identifier: Apache-2.023mod serial_utils;45use std::io;6use std::os::raw::{c_int, c_void};7use std::sync::{Arc, Mutex};89use event_manager::{EventManager, SubscriberOps};1011use devices::legacy::Serial;12use devices::BusDevice;13use serial_utils::MockSerialInput;14use utils::eventfd::EventFd;1516#[test]17fn test_issue_serial_hangup_anon_pipe_while_registered_stdin() {18let mut fds: [c_int; 2] = [0; 2];19let rc = unsafe { libc::pipe(fds.as_mut_ptr()) };20assert!(rc == 0);2122// Serial input is the reading end of the pipe.23let serial_in = MockSerialInput(fds[0]);24let kick_stdin_evt = EventFd::new(libc::EFD_NONBLOCK).unwrap();25let serial = Arc::new(Mutex::new(Serial::new_in_out(26EventFd::new(libc::EFD_NONBLOCK).unwrap(),27Box::new(serial_in),28Box::new(io::stdout()),29Some(kick_stdin_evt.try_clone().unwrap()),30)));3132// Make reading fd non blocking to read just what is inflight.33let flags = unsafe { libc::fcntl(fds[0], libc::F_GETFL, 0) };34let mut rc = unsafe { libc::fcntl(fds[0], libc::F_SETFL, flags | libc::O_NONBLOCK) };35assert!(rc == 0);3637const BYTES_COUNT: usize = 65; // Serial FIFO_SIZE + 1.38let mut dummy_data = [1u8; BYTES_COUNT];39rc = unsafe {40libc::write(41fds[1],42dummy_data.as_mut_ptr() as *const c_void,43dummy_data.len(),44) as i3245};46assert!(dummy_data.len() == rc as usize);4748// Register the reading end of the pipe to the event manager, to be processed later on.49let mut event_manager = EventManager::new().unwrap();50let _id = event_manager.add_subscriber(serial.clone());5152// `EventSet::IN` was received on stdin. The event handling will consume53// 64 bytes from stdin. The stdin monitoring is still armed.54let mut ev_count = event_manager.run().unwrap();55assert_eq!(ev_count, 1);5657let mut data = [0u8; BYTES_COUNT];5859// On the main thread, we will simulate guest "vCPU" thread serial reads.60let data_bus_offset = 0;61for i in 0..BYTES_COUNT - 1 {62serial63.lock()64.unwrap()65.read(data_bus_offset, &mut data[i..=i]);66}6768assert!(data[..31] == dummy_data[..31]);69assert!(data[32..64] == dummy_data[32..64]);7071// The avail capacity of the serial FIFO is 64.72// Read the 65th from the stdin through the kick stdin event triggered by 64th of the serial73// FIFO read, or by the armed level-triggered stdin monitoring. Either one of the events might74// be handled first. The handling of the second event will find the stdin without any pending75// bytes and will result in EWOULDBLOCK. Usually, EWOULDBLOCK will reregister the stdin, but76// since it was not unregistered before, it will do a noop.77ev_count = event_manager.run().unwrap();78assert_eq!(ev_count, 2);7980// The avail capacity of the serial FIFO is 63.81rc = unsafe {82libc::write(83fds[1],84dummy_data.as_mut_ptr() as *const c_void,85dummy_data.len(),86) as i3287};88assert!(dummy_data.len() == rc as usize);8990// Writing to the other end of the pipe triggers handling a stdin event.91// Now, 63 bytes will be read from stdin, filling up the buffer.92ev_count = event_manager.run().unwrap();93assert_eq!(ev_count, 1);9495// Close the writing end (this sends an HANG_UP to the reading end).96// While the stdin is registered, this event is caught by the event manager.97rc = unsafe { libc::close(fds[1]) };98assert!(rc == 0);99100// This cycle of epoll has two important events. First, the received HANGUP and second101// the fact that the FIFO is full, so even if the stdin reached EOF, there are still102// pending bytes to be read. We still unregister the stdin and keep reading from it until103// we get all pending bytes.104ev_count = event_manager.run().unwrap();105assert_eq!(ev_count, 1);106107// Free up 64 bytes from the serial FIFO.108for i in 0..BYTES_COUNT - 1 {109serial110.lock()111.unwrap()112.read(data_bus_offset, &mut data[i..=i]);113}114115// Process the kick stdin event generated by the reading of the 64th byte of the serial FIFO.116// This will consume some more bytes from the stdin while the stdin is unregistered.117ev_count = event_manager.run().unwrap();118assert_eq!(ev_count, 1);119120// Two more bytes left. At the 2nd byte, another kick read stdin event is generated,121// trying to fill again the serial FIFO with more bytes.122for i in 0..2 {123serial124.lock()125.unwrap()126.read(data_bus_offset, &mut data[i..=i]);127}128129// We try to read again, but we detect that stdin received previously EOF.130// This can be deduced by reading from a non-blocking fd and getting 0 bytes as a result,131// instead of EWOUDBLOCK. We unregister the stdin and the kick stdin read evt.132ev_count = event_manager.run().unwrap();133assert_eq!(ev_count, 1);134135// Nothing can interrupt us.136ev_count = event_manager.run_with_timeout(1).unwrap();137assert_eq!(ev_count, 0);138}139140#[test]141fn test_issue_hangup() {142let mut fds: [c_int; 2] = [0; 2];143let rc = unsafe { libc::pipe(fds.as_mut_ptr()) };144assert!(rc == 0);145146// Serial input is the reading end of the pipe.147let serial_in = MockSerialInput(fds[0]);148let kick_stdin_evt = EventFd::new(libc::EFD_NONBLOCK).unwrap();149let serial = Arc::new(Mutex::new(Serial::new_in_out(150EventFd::new(libc::EFD_NONBLOCK).unwrap(),151Box::new(serial_in),152Box::new(io::stdout()),153Some(kick_stdin_evt.try_clone().unwrap()),154)));155156// Make reading fd non blocking to read just what is inflight.157let flags = unsafe { libc::fcntl(fds[0], libc::F_GETFL, 0) };158let mut rc = unsafe { libc::fcntl(fds[0], libc::F_SETFL, flags | libc::O_NONBLOCK) };159assert!(rc == 0);160161// Close the writing end (this sends an HANG_UP to the reading end).162// While the stdin is registered, this event is caught by the event manager.163rc = unsafe { libc::close(fds[1]) };164assert!(rc == 0);165166// Register the reading end of the pipe to the event manager, to be processed later on.167let mut event_manager = EventManager::new().unwrap();168let _id = event_manager.add_subscriber(serial.clone());169170let mut ev_count = event_manager.run().unwrap();171assert_eq!(ev_count, 1);172173// Nothing can interrupt us.174ev_count = event_manager.run_with_timeout(1).unwrap();175assert_eq!(ev_count, 0);176}177178#[test]179fn test_issue_serial_hangup_anon_pipe_while_unregistered_stdin() {180let mut fds: [c_int; 2] = [0; 2];181let rc = unsafe { libc::pipe(fds.as_mut_ptr()) };182assert!(rc == 0);183184// Serial input is the reading end of the pipe.185let serial_in = MockSerialInput(fds[0]);186let kick_stdin_evt = EventFd::new(libc::EFD_NONBLOCK).unwrap();187let serial = Arc::new(Mutex::new(Serial::new_in_out(188EventFd::new(libc::EFD_NONBLOCK).unwrap(),189Box::new(serial_in),190Box::new(io::stdout()),191Some(kick_stdin_evt.try_clone().unwrap()),192)));193194// Make reading fd non blocking to read just what is inflight.195let flags = unsafe { libc::fcntl(fds[0], libc::F_GETFL, 0) };196let mut rc = unsafe { libc::fcntl(fds[0], libc::F_SETFL, flags | libc::O_NONBLOCK) };197assert!(rc == 0);198199const BYTES_COUNT: usize = 65; // Serial FIFO_SIZE + 1.200let mut dummy_data = [1u8; BYTES_COUNT];201rc = unsafe {202libc::write(203fds[1],204dummy_data.as_mut_ptr() as *const c_void,205dummy_data.len(),206) as i32207};208assert!(dummy_data.len() == rc as usize);209210// Register the reading end of the pipe to the event manager, to be processed later on.211let mut event_manager = EventManager::new().unwrap();212let _id = event_manager.add_subscriber(serial.clone());213214// `EventSet::IN` was received on stdin. The event handling will consume215// 64 bytes from stdin. The stdin monitoring is still armed.216let mut ev_count = event_manager.run_with_timeout(0).unwrap();217assert_eq!(ev_count, 1);218219let mut data = [0u8; BYTES_COUNT];220221// On the main thread, we will simulate guest "vCPU" thread serial reads.222let data_bus_offset = 0;223for i in 0..BYTES_COUNT - 1 {224serial225.lock()226.unwrap()227.read(data_bus_offset, &mut data[i..=i]);228}229230assert!(data[..31] == dummy_data[..31]);231assert!(data[32..64] == dummy_data[32..64]);232233// The avail capacity of the serial FIFO is 64.234// Read the 65th from the stdin through the kick stdin event triggered by 64th of the serial235// FIFO read, or by the armed level-triggered stdin monitoring. Either one of the events might236// be handled first. The handling of the second event will find the stdin without any pending237// bytes and will result in EWOULDBLOCK. Usually, EWOULDBLOCK will reregister the stdin, but238// since it was not unregistered before, it will do a noop.239ev_count = event_manager.run().unwrap();240assert_eq!(ev_count, 2);241242// The avail capacity of the serial FIFO is 63.243rc = unsafe {244libc::write(245fds[1],246dummy_data.as_mut_ptr() as *const c_void,247dummy_data.len(),248) as i32249};250assert!(dummy_data.len() == rc as usize);251252// Writing to the other end of the pipe triggers handling an stdin event.253// Now, 63 bytes will be read from stdin, filling up the buffer.254ev_count = event_manager.run().unwrap();255assert_eq!(ev_count, 1);256257// Serial FIFO is full, so silence the stdin. We do not need any other interruptions258// until the serial FIFO is freed.259ev_count = event_manager.run().unwrap();260assert_eq!(ev_count, 1);261262// Close the writing end (this sends an HANG_UP to the reading end).263// While the stdin is unregistered, this event is not caught by the event manager.264rc = unsafe { libc::close(fds[1]) };265assert!(rc == 0);266267// This would be a blocking epoll_wait, since the buffer is full and stdin is unregistered.268// There is no event that can break the epoll wait loop.269ev_count = event_manager.run_with_timeout(0).unwrap();270assert_eq!(ev_count, 0);271272// Free up 64 bytes from the serial FIFO.273for i in 0..BYTES_COUNT - 1 {274serial275.lock()276.unwrap()277.read(data_bus_offset, &mut data[i..=i]);278}279280// Process the kick stdin event generated by the reading of the 64th byte of the serial FIFO.281// This will consume some more bytes from the stdin. Keep in mind that the HANGUP event was282// lost and we do not know that the stdin reached EOF.283ev_count = event_manager.run().unwrap();284assert_eq!(ev_count, 1);285286// Two more bytes left. At the 2nd byte, another kick read stdin event is generated,287// trying to fill again the serial FIFO with more bytes. Keep in mind that the HANGUP event was288// lost and we do not know that the stdin reached EOF.289for i in 0..2 {290serial291.lock()292.unwrap()293.read(data_bus_offset, &mut data[i..=i]);294}295296// We try to read again, but we detect that stdin received previously EOF.297// This can be deduced by reading from a non-blocking fd and getting 0 bytes as a result,298// instead of EWOUDBLOCK. We unregister the stdin and the kick stdin read evt.299ev_count = event_manager.run().unwrap();300assert_eq!(ev_count, 1);301302// Nothing can interrupt us.303ev_count = event_manager.run_with_timeout(0).unwrap();304assert_eq!(ev_count, 0);305}306307308