in src/enclave_proc/mod.rs [420:544]
fn process_event_loop(
comm_stream: UnixStream,
logger: &EnclaveProcLogWriter,
) -> NitroCliResult<()> {
let mut conn_listener = ConnectionListener::new()?;
let mut enclave_manager = EnclaveManager::default();
let mut terminate_thread: Option<std::thread::JoinHandle<()>> = None;
let mut describe_thread: DescribeThread = None;
let mut done = false;
let mut ret_value = Ok(());
// Start the signal handler before spawning any other threads. This is done since the
// handler will mask all relevant signals from the current thread and this setting will
// be automatically inherited by all threads spawned from this point on; we want this
// because only the dedicated thread spawned by the handler should listen for signals.
enclave_proc_configure_signal_handler(&conn_listener)
.map_err(|e| e.add_subaction("Failed to configure signal handler".to_string()))?;
// Add the CLI communication channel to epoll.
conn_listener
.handle_new_connection(comm_stream)
.map_err(|e| {
e.add_subaction("Failed to add CLI communication channel to epoll".to_string())
})?;
while !done {
// We can get connections to CLI instances, to the enclave or to ourselves.
let connection =
conn_listener.get_next_connection(enclave_manager.get_enclave_descriptor().ok())?;
// If this is an enclave event, handle it.
match try_handle_enclave_event(&connection) {
Ok(HandledEnclaveEvent::HangUp) => break,
Ok(HandledEnclaveEvent::Unexpected) => continue,
Ok(HandledEnclaveEvent::None) => (),
Err(error_info) => {
ret_value = Err(error_info
.add_subaction("Error while trying to handle enclave event".to_string()));
break;
}
}
// At this point we have a connection that is not coming from an enclave.
// Read the command that should be executed.
let cmd = match connection.read_command() {
Ok(value) => value,
Err(mut error_info) => {
error_info = error_info
.add_subaction("Failed to read command".to_string())
.set_action("Run Enclave".to_string());
notify_error_with_conn(
&construct_error_message(&error_info),
&connection,
EnclaveProcessCommandType::NotPermitted,
);
break;
}
};
info!("Received command: {:?}", cmd);
let status = handle_command(
cmd,
logger,
&connection,
&mut conn_listener,
&mut enclave_manager,
&mut terminate_thread,
&mut describe_thread,
);
// Obtain the status code and whether the event loop must be exited.
let (status_code, do_break) = match status {
Ok(value) => value,
Err(mut error_info) => {
// Any encountered error is both logged and send to the other side of the connection.
error_info = error_info
.add_subaction(format!("Failed to execute command `{:?}`", cmd))
.set_action("Run Enclave".to_string());
notify_error_with_conn(&construct_error_message(&error_info), &connection, cmd);
(libc::EINVAL, true)
}
};
done = do_break;
// Perform clean-up and stop the connection listener before returning the status to the CLI.
// This is done to avoid race conditions where the enclave process has not yet removed the
// socket and another CLI issues a command on that very-soon-to-be-removed socket.
if done {
// Stop the connection listener.
conn_listener.stop()?;
// Wait for the termination thread, if any.
if terminate_thread.is_some() {
terminate_thread.take().unwrap().join().map_err(|e| {
new_nitro_cli_failure!(
&format!("Termination thread join failed: {:?}", e),
NitroCliErrorEnum::ThreadJoinFailure
)
})?;
};
}
// Only the commands coming from the CLI must be replied to with the status code.
match cmd {
EnclaveProcessCommandType::Run
| EnclaveProcessCommandType::Terminate
| EnclaveProcessCommandType::Describe
| EnclaveProcessCommandType::GetEnclaveName
| EnclaveProcessCommandType::GetIDbyName => {
connection.write_status(status_code).map_err(|_| {
new_nitro_cli_failure!(
"Process event loop failed",
NitroCliErrorEnum::EnclaveProcessSendReplyFailure
)
})?
}
_ => (),
}
}
info!("Enclave process {} exited event loop.", process::id());
ret_value
}