nitro_cli/enclave_proc/
socket.rs1#![deny(missing_docs)]
4#![deny(warnings)]
5
6use inotify::{EventMask, Inotify, WatchMask};
7use log::{debug, warn};
8use std::path::{Path, PathBuf};
9use std::sync::atomic::{AtomicBool, Ordering};
10use std::sync::Arc;
11use std::thread::{self, JoinHandle};
12
13use crate::common::get_socket_path;
14use crate::common::{ExitGracefully, NitroCliErrorEnum, NitroCliFailure, NitroCliResult};
15use crate::new_nitro_cli_failure;
16
17#[derive(Default)]
19pub struct EnclaveProcSock {
20 socket_path: PathBuf,
22 remove_listener_thread: Option<JoinHandle<()>>,
24 requested_remove: Arc<AtomicBool>,
26}
27
28impl Clone for EnclaveProcSock {
30 fn clone(&self) -> Self {
31 EnclaveProcSock {
33 socket_path: self.socket_path.clone(),
34 remove_listener_thread: None,
35 requested_remove: self.requested_remove.clone(),
36 }
37 }
38}
39
40impl Drop for EnclaveProcSock {
41 fn drop(&mut self) {
42 self.close_mut()
43 .ok_or_exit_with_errno(Some("Failed to drop socket"));
44 }
45}
46
47impl EnclaveProcSock {
48 pub fn new(enclave_id: &str) -> NitroCliResult<Self> {
50 let socket_path = get_socket_path(enclave_id).map_err(|_| {
51 new_nitro_cli_failure!(
52 "Failed to create enclave process socket",
53 NitroCliErrorEnum::SocketPathNotFound
54 )
55 })?;
56
57 Ok(EnclaveProcSock {
58 socket_path,
59 remove_listener_thread: None,
60 requested_remove: Arc::new(AtomicBool::new(false)),
61 })
62 }
63
64 pub fn get_path(&self) -> &Path {
66 self.socket_path.as_path()
67 }
68
69 pub fn set_path(&mut self, socket_path: PathBuf) {
71 self.socket_path = socket_path;
72 }
73
74 pub fn start_monitoring(&mut self, exit_on_delete: bool) -> NitroCliResult<()> {
76 let path_clone = self.socket_path.clone();
77 let requested_remove_clone = self.requested_remove.clone();
78 let socket_inotify = Inotify::init().map_err(|e| {
79 new_nitro_cli_failure!(
80 &format!("Failed to initialize socket notifications: {e:?}"),
81 NitroCliErrorEnum::InotifyError
82 )
83 })?;
84
85 socket_inotify
89 .watches()
90 .add(
91 self.socket_path.as_path(),
92 WatchMask::ATTRIB | WatchMask::DELETE_SELF,
93 )
94 .map_err(|e| {
95 new_nitro_cli_failure!(
96 &format!("Failed to add watch to inotify: {e:?}"),
97 NitroCliErrorEnum::InotifyError
98 )
99 })?;
100 self.remove_listener_thread = Some(thread::spawn(move || {
101 socket_removal_listener(
102 path_clone,
103 requested_remove_clone,
104 socket_inotify,
105 exit_on_delete,
106 )
107 }));
108 Ok(())
109 }
110
111 fn close_mut(&mut self) -> NitroCliResult<()> {
113 self.requested_remove.store(true, Ordering::SeqCst);
116 if self.socket_path.exists() {
117 std::fs::remove_file(&self.socket_path).map_err(|e| {
118 new_nitro_cli_failure!(
119 &format!(
120 "Failed to remove socket file {:?} from disk: {:?}",
121 self.socket_path, e
122 ),
123 NitroCliErrorEnum::FileOperationFailure
124 )
125 .add_info(vec![
126 self.socket_path
127 .to_str()
128 .unwrap_or("Invalid unicode socket file name"),
129 "Remove",
130 ])
131 })?;
132 }
133
134 if self.remove_listener_thread.is_some() {
136 self.remove_listener_thread
137 .take()
138 .unwrap()
139 .join()
140 .map_err(|e| {
141 new_nitro_cli_failure!(
142 &format!("Failed to join socket notification thread: {e:?}"),
143 NitroCliErrorEnum::ThreadJoinFailure
144 )
145 })?;
146 }
147
148 Ok(())
149 }
150
151 pub fn close(mut self) -> NitroCliResult<()> {
153 self.close_mut()
154 .map_err(|e| e.add_subaction("Close socket".to_string()))
155 }
156}
157
158fn socket_removal_listener(
160 socket_path: PathBuf,
161 requested_remove: Arc<AtomicBool>,
162 mut socket_inotify: Inotify,
163 exit_on_delete: bool,
164) {
165 let mut buffer = [0u8; 4096];
166 let mut done = false;
167
168 debug!("Socket file event listener started for {:?}.", socket_path);
169
170 while !done {
171 let events = socket_inotify
173 .read_events_blocking(&mut buffer)
174 .map_err(|e| {
175 new_nitro_cli_failure!(
176 &format!("Socket removal listener error: {e:?}"),
177 NitroCliErrorEnum::InotifyError
178 )
179 .set_action("Run Enclave".to_string())
180 })
181 .ok_or_exit_with_errno(Some("Failed to read inotify events"));
182
183 for event in events {
184 if (event.mask.contains(EventMask::ATTRIB)
188 || event.mask.contains(EventMask::DELETE_SELF))
189 && !socket_path.exists()
190 {
191 if requested_remove.load(Ordering::SeqCst) {
192 debug!("The enclave process socket has deleted itself.");
195 done = true;
196 } else {
197 warn!("The enclave process socket has been deleted!");
201 if exit_on_delete {
202 std::process::exit(1);
203 }
204 done = true;
205 }
206 }
207 }
208 }
209
210 debug!("Enclave process socket monitoring is done.");
211}
212
213#[cfg(test)]
214mod tests {
215 use super::*;
216
217 use std::os::unix::net::UnixListener;
218 use std::process::Command;
219
220 const DUMMY_ENCLAVE_ID: &str = "i-0000000000000000-enc0123456789012345";
221 const THREADS_STR: &str = "Threads:";
222 const WAIT_REMOVE_MILLIS: u64 = 10;
223
224 fn get_num_threads_from_status_output(status_str: String) -> u32 {
228 let start_idx = status_str.find(THREADS_STR);
229 let mut iter = status_str.chars();
230 iter.by_ref().nth(start_idx.unwrap() + THREADS_STR.len()); let slice = iter.as_str();
232
233 let new_str = slice.to_string();
234 let end_idx = new_str.find('\n'); let substr = &slice[..end_idx.unwrap()];
236
237 substr.parse().unwrap()
238 }
239
240 #[test]
243 fn test_enclaveprocsock_init() {
244 let socket = EnclaveProcSock::new(DUMMY_ENCLAVE_ID);
245
246 assert!(socket.is_ok());
247
248 if let Ok(socket) = socket {
249 assert!(socket
250 .socket_path
251 .as_path()
252 .to_str()
253 .unwrap()
254 .contains("0123456789012345"));
255 assert!(socket.remove_listener_thread.is_none());
256 assert!(!socket.requested_remove.load(Ordering::SeqCst));
257 }
258 }
259
260 #[test]
263 fn test_start_monitoring() {
264 let socket = EnclaveProcSock::new(DUMMY_ENCLAVE_ID);
265
266 assert!(socket.is_ok());
267
268 if let Ok(mut socket) = socket {
269 UnixListener::bind(socket.get_path())
270 .map_err(|e| {
271 new_nitro_cli_failure!(
272 &format!("Failed to bind to socket: {e:?}"),
273 NitroCliErrorEnum::SocketError
274 )
275 })
276 .ok_or_exit_with_errno(Some("Error binding"));
277 let result = socket.start_monitoring(false);
278
279 assert!(result.is_ok());
280
281 std::fs::remove_file(&socket.socket_path).unwrap();
283 std::thread::sleep(std::time::Duration::from_millis(WAIT_REMOVE_MILLIS));
284
285 assert!(!socket.requested_remove.load(Ordering::SeqCst));
286 }
287 }
288
289 #[test]
292 fn test_close() {
293 let socket = EnclaveProcSock::new(DUMMY_ENCLAVE_ID);
294
295 assert!(socket.is_ok());
296
297 let out_cmd0 = Command::new("cat")
299 .arg(format!("/proc/{}/status", std::process::id()))
300 .output()
301 .expect("Failed to run cat");
302 let out0 = std::str::from_utf8(&out_cmd0.stdout).unwrap();
303 let crt_num_threads0 = get_num_threads_from_status_output(out0.to_string());
304
305 if let Ok(mut socket) = socket {
306 let _ = UnixListener::bind(socket.get_path())
307 .map_err(|e| {
308 new_nitro_cli_failure!(
309 &format!("Failed to bind to socket: {e:?}"),
310 NitroCliErrorEnum::SocketError
311 )
312 })
313 .ok_or_exit_with_errno(Some("Error binding"));
314 let result = socket.start_monitoring(true);
315
316 assert!(result.is_ok());
317
318 let result = socket.close_mut();
320
321 assert!(result.is_ok());
322 assert!(socket.requested_remove.load(Ordering::SeqCst));
323 }
324
325 let out_cmd1 = Command::new("cat")
327 .arg(format!("/proc/{}/status", std::process::id()))
328 .output()
329 .expect("Failed to run cat");
330 let out1 = std::str::from_utf8(&out_cmd1.stdout).unwrap();
331 let crt_num_threads1 = get_num_threads_from_status_output(out1.to_string());
332
333 assert_eq!(crt_num_threads0, crt_num_threads1);
335 }
336}