1use std::collections::HashMap;
4use std::env;
5use std::fs::{File, OpenOptions};
6use std::os::fd::AsRawFd;
7use std::sync::Arc;
8use std::sync::atomic::{AtomicBool, Ordering};
9use std::time::Instant;
10
11use chrono::Utc;
12use tokio::io::unix::AsyncFd;
13use tokio::sync::{mpsc, watch};
14use tokio::time::{self, Duration};
15
16use microsandbox_protocol::HANDOFF_POWEROFF_TIMEOUT;
17use microsandbox_protocol::codec::{self, MAX_FRAME_SIZE};
18use microsandbox_protocol::core::{
19 ClockSync, CoreError, CoreErrorKind, InitAck, InitResolved, Ping, Pong, Ready,
20 RelayClientDisconnected, ResolvedUser, Touch, Touched,
21};
22use microsandbox_protocol::exec::{
23 ExecExited, ExecFailed, ExecFailureKind, ExecRequest, ExecResize, ExecSignal, ExecStarted,
24 ExecStderr, ExecStdin, ExecStdinError, ExecStdout,
25};
26use microsandbox_protocol::fs::{FsData, FsRequest};
27use microsandbox_protocol::heartbeat::{ActivityCounters, Heartbeat};
28use microsandbox_protocol::message::{Message, MessageType};
29use microsandbox_protocol::tcp::{TcpClose, TcpConnect, TcpData, TcpEof, TcpFailed};
30
31use crate::config::AgentdConfig;
32use crate::error::{AgentdError, AgentdResult};
33use crate::fs::{FsReadSession, FsState, FsStreamSession, FsWriteSession};
34use crate::serial::AGENT_PORT_NAME;
35use crate::session::{
36 ExecSession, RawActivity, RawSessionCompletion, SessionOutput, resolve_default_user,
37};
38use crate::tcp::TcpSession;
39use crate::{clock, fs, handoff, heartbeat, serial};
40
41const HEARTBEAT_INTERVAL_SECS: u64 = 1;
50
51const SERIAL_READ_BUF_SIZE: usize = 64 * 1024;
53
54const MAX_INPUT_BUF_SIZE: usize = MAX_FRAME_SIZE as usize + 4;
56
57const INIT_ACK_TIMEOUT_SECS: u64 = 60;
59
60#[derive(Default)]
65struct AgentState {
66 sessions: HashMap<u32, ExecSession>,
67 write_sessions: HashMap<u32, FsWriteSession>,
68 read_sessions: HashMap<u32, FsReadSession>,
69 tcp_sessions: HashMap<u32, TcpSession>,
70 fs: FsState,
71}
72
73struct ActivityTracker {
74 activity_seq: u64,
75 counters: ActivityCounters,
76}
77
78#[derive(Clone)]
79struct HeartbeatSnapshot {
80 activity_seq: u64,
81 active_exec_sessions: u32,
82 active_fs_streams: u32,
83 active_tcp_streams: u32,
84 counters: ActivityCounters,
85}
86
87impl ActivityTracker {
92 fn new() -> Self {
93 Self {
94 activity_seq: 0,
95 counters: ActivityCounters::default(),
96 }
97 }
98
99 fn record_host_message(&mut self) {
100 self.touch();
101 self.counters.host_messages = self.counters.host_messages.saturating_add(1);
102 }
103
104 fn record_guest_message(&mut self) {
105 self.touch();
106 self.counters.guest_messages = self.counters.guest_messages.saturating_add(1);
107 }
108
109 fn add_exec_output_bytes(&mut self, len: usize) {
110 self.counters.exec_output_bytes =
111 self.counters.exec_output_bytes.saturating_add(len as u64);
112 }
113
114 fn add_fs_bytes(&mut self, len: usize) {
115 self.counters.fs_bytes = self.counters.fs_bytes.saturating_add(len as u64);
116 }
117
118 fn add_tcp_bytes(&mut self, len: usize) {
119 self.counters.tcp_bytes = self.counters.tcp_bytes.saturating_add(len as u64);
120 }
121
122 fn touch(&mut self) {
123 self.activity_seq = self.activity_seq.saturating_add(1);
124 }
125}
126
127pub async fn run(
139 boot_time_ns: u64,
140 init_time_ns: u64,
141 config: &AgentdConfig,
142 port_file: File,
143) -> AgentdResult<()> {
144 let port_fd = port_file.as_raw_fd();
147 set_nonblocking(port_fd)?;
148
149 let async_port = AsyncFd::new(port_file)?;
151
152 let mut read_buf = vec![0u8; SERIAL_READ_BUF_SIZE];
154 let mut serial_in_buf = Vec::new();
155 let mut serial_out_buf = Vec::new();
156
157 let mut state = AgentState::default();
158
159 let (session_tx, mut session_rx) = mpsc::unbounded_channel::<(u32, SessionOutput)>();
161
162 let mut activity = ActivityTracker::new();
164 let (heartbeat_tx, heartbeat_rx) = watch::channel(heartbeat_snapshot(&state, &activity));
165 let heartbeat_shutdown = Arc::new(AtomicBool::new(false));
172 let heartbeat_thread = spawn_heartbeat_thread(heartbeat_rx, Arc::clone(&heartbeat_shutdown));
173
174 let ready_time_ns = clock::boottime_ns();
176 let ready_msg = Message::with_payload(
177 MessageType::Ready,
178 0,
179 &Ready {
180 boot_time_ns,
181 init_time_ns,
182 ready_time_ns,
183 agent_version: env!("CARGO_PKG_VERSION").to_string(),
184 },
185 )
186 .map_err(|e| AgentdError::ExecSession(format!("encode ready: {e}")))?;
187 codec::encode_to_buf(&ready_msg, &mut serial_out_buf)
188 .map_err(|e| AgentdError::ExecSession(format!("encode ready frame: {e}")))?;
189 flush_write_buf(&async_port, &mut serial_out_buf).await?;
190
191 'agent: loop {
193 tokio::select! {
194 result = async_port.readable() => {
196 let Ok(mut guard) = result else {
197 break;
198 };
199
200 loop {
201 match guard.try_io(|inner| read_from_fd(inner.get_ref().as_raw_fd(), &mut read_buf)) {
202 Ok(Ok(0)) => {
203 if !handoff::is_pid_1() {
205 guard.clear_ready();
206 drop(guard);
207 time::sleep(Duration::from_millis(100)).await;
208 break;
209 }
210 break 'agent;
211 }
212 Ok(Ok(n)) => {
213 serial_in_buf.extend_from_slice(&read_buf[..n]);
214
215 if serial_in_buf.len() > MAX_INPUT_BUF_SIZE {
217 return Err(AgentdError::ExecSession(
218 "serial input buffer exceeded maximum size".into(),
219 ));
220 }
221
222 while let Some(frame) = codec::try_decode_raw_from_buf(&mut serial_in_buf)
227 .map_err(|e| AgentdError::ExecSession(format!("decode frame: {e}")))?
228 {
229 let id = frame.id;
230 let msg = match codec::raw_frame_to_message(frame) {
231 Ok(msg) => msg,
232 Err(e) => {
233 return Err(AgentdError::ExecSession(format!(
234 "decode message for id {id}: {e}"
235 )));
236 }
237 };
238
239 if msg.flags != msg.t.flags() {
240 let out_before = serial_out_buf.len();
241 encode_core_error_if_supported(
242 &msg,
243 msg.id,
244 CoreErrorKind::InvalidFlags,
245 format!(
246 "invalid flags for {}: got {}, expected {}",
247 msg.t.as_str(),
248 msg.flags,
249 msg.t.flags()
250 ),
251 Some(msg.t.as_str().to_string()),
252 &mut serial_out_buf,
253 )?;
254 record_encoded_guest_messages(
255 &serial_out_buf,
256 out_before,
257 &mut activity,
258 );
259 publish_heartbeat_snapshot(&heartbeat_tx, &state, &activity);
260 continue;
261 }
262
263 if message_refreshes_idle_timer(&msg.t) {
264 activity.record_host_message();
265 publish_heartbeat_snapshot(&heartbeat_tx, &state, &activity);
266 }
267
268 let out_before = serial_out_buf.len();
269 handle_message(
270 msg,
271 &mut state,
272 &mut activity,
273 &session_tx,
274 &mut serial_out_buf,
275 config,
276 ).await?;
277 record_encoded_guest_messages(
278 &serial_out_buf,
279 out_before,
280 &mut activity,
281 );
282 publish_heartbeat_snapshot(&heartbeat_tx, &state, &activity);
283 }
284
285 if !serial_out_buf.is_empty() {
287 flush_write_buf(&async_port, &mut serial_out_buf).await?;
288 }
289 }
290 Ok(Err(e)) if e.kind() == std::io::ErrorKind::Interrupted => continue,
291 Ok(Err(_)) if !handoff::is_pid_1() => {
292 guard.clear_ready();
293 drop(guard);
294 time::sleep(Duration::from_millis(100)).await;
295 break;
296 }
297 Ok(Err(e)) => return Err(e.into()),
298 Err(_would_block) => break,
299 }
300 }
301 }
302
303 Some((id, output)) = session_rx.recv() => {
305 match output {
306 SessionOutput::Stdout(data) => {
307 let len = data.len();
308 let msg = Message::with_payload(MessageType::ExecStdout, id, &ExecStdout { data })
309 .map_err(|e| AgentdError::ExecSession(format!("encode stdout: {e}")))?;
310 codec::encode_to_buf(&msg, &mut serial_out_buf)
311 .map_err(|e| AgentdError::ExecSession(format!("encode stdout frame: {e}")))?;
312 activity.record_guest_message();
313 activity.add_exec_output_bytes(len);
314 }
315 SessionOutput::Stderr(data) => {
316 let len = data.len();
317 let msg = Message::with_payload(MessageType::ExecStderr, id, &ExecStderr { data })
318 .map_err(|e| AgentdError::ExecSession(format!("encode stderr: {e}")))?;
319 codec::encode_to_buf(&msg, &mut serial_out_buf)
320 .map_err(|e| AgentdError::ExecSession(format!("encode stderr frame: {e}")))?;
321 activity.record_guest_message();
322 activity.add_exec_output_bytes(len);
323 }
324 SessionOutput::Exited(code) => {
325 let msg = Message::with_payload(MessageType::ExecExited, id, &ExecExited { code })
326 .map_err(|e| AgentdError::ExecSession(format!("encode exited: {e}")))?;
327 codec::encode_to_buf(&msg, &mut serial_out_buf)
328 .map_err(|e| AgentdError::ExecSession(format!("encode exited frame: {e}")))?;
329 state.sessions.remove(&id);
330 activity.record_guest_message();
331 }
332 SessionOutput::Raw(output) => {
333 apply_raw_activity(output.activity, &mut activity);
334 complete_raw_session(
335 id,
336 output.completion,
337 &mut state.read_sessions,
338 &mut state.tcp_sessions,
339 );
340 serial_out_buf.extend_from_slice(&output.frame);
342 }
343 }
344 publish_heartbeat_snapshot(&heartbeat_tx, &state, &activity);
345
346 if !serial_out_buf.is_empty() {
347 flush_write_buf(&async_port, &mut serial_out_buf).await?;
348 }
349 }
350 }
351 }
352
353 heartbeat_shutdown.store(true, Ordering::Relaxed);
354 let _ = heartbeat_thread.join();
355
356 Ok(())
357}
358
359pub fn open_serial_port() -> AgentdResult<File> {
361 let port_path = serial::find_serial_port(AGENT_PORT_NAME)?;
363
364 Ok(OpenOptions::new().read(true).write(true).open(&port_path)?)
367}
368
369pub fn report_init_context(port_file: &File, default_user: Option<&str>) -> AgentdResult<()> {
371 let (uid, gid) = resolve_default_user(default_user)?;
372 let deadline = init_ack_deadline();
373 let fd = port_file.as_raw_fd();
374 set_nonblocking(fd)?;
375
376 let msg = Message::with_payload(
377 MessageType::InitResolved,
378 0,
379 &InitResolved {
380 default_user: ResolvedUser { uid, gid },
381 },
382 )
383 .map_err(|e| AgentdError::ExecSession(format!("encode init context: {e}")))?;
384
385 let mut out = Vec::new();
386 codec::encode_to_buf(&msg, &mut out)
387 .map_err(|e| AgentdError::ExecSession(format!("encode init context frame: {e}")))?;
388 write_all_to_fd(fd, &out, deadline)?;
389 wait_for_init_ack(fd, deadline)
390}
391
392async fn handle_message(
398 msg: Message,
399 state: &mut AgentState,
400 activity: &mut ActivityTracker,
401 session_tx: &mpsc::UnboundedSender<(u32, SessionOutput)>,
402 out_buf: &mut Vec<u8>,
403 config: &AgentdConfig,
404) -> AgentdResult<()> {
405 match msg.t {
406 MessageType::Ping => {
407 let Some(_) = decode_payload_or_core_error::<Ping>(&msg, out_buf)? else {
408 return Ok(());
409 };
410 let reply = Message::with_payload(MessageType::Pong, msg.id, &Pong {})
411 .map_err(|e| AgentdError::ExecSession(format!("encode pong: {e}")))?;
412 codec::encode_to_buf(&reply, out_buf)
413 .map_err(|e| AgentdError::ExecSession(format!("encode pong frame: {e}")))?;
414 }
415
416 MessageType::Touch => {
417 let Some(_) = decode_payload_or_core_error::<Touch>(&msg, out_buf)? else {
418 return Ok(());
419 };
420 activity.record_host_message();
421 let reply = Message::with_payload(
422 MessageType::Touched,
423 msg.id,
424 &Touched {
425 activity_seq: activity.activity_seq,
426 },
427 )
428 .map_err(|e| AgentdError::ExecSession(format!("encode touched: {e}")))?;
429 codec::encode_to_buf(&reply, out_buf)
430 .map_err(|e| AgentdError::ExecSession(format!("encode touched frame: {e}")))?;
431 }
432
433 MessageType::ExecRequest => {
434 let Some(mut req) = decode_payload_or_core_error::<ExecRequest>(&msg, out_buf)? else {
435 return Ok(());
436 };
437 prepend_scripts_to_path(&mut req);
438 match ExecSession::spawn(
439 msg.id,
440 &req,
441 session_tx.clone(),
442 config.user.as_deref(),
443 config.security_profile,
444 ) {
445 Ok(session) => {
446 let reply = Message::with_payload(
447 MessageType::ExecStarted,
448 msg.id,
449 &ExecStarted { pid: session.pid() },
450 )
451 .map_err(|e| AgentdError::ExecSession(format!("encode started: {e}")))?;
452 codec::encode_to_buf(&reply, out_buf).map_err(|e| {
453 AgentdError::ExecSession(format!("encode started frame: {e}"))
454 })?;
455 state.sessions.insert(msg.id, session);
456 }
457 Err(e) => {
458 let payload = match &e {
464 AgentdError::ExecSpawnFailed(p) => p.clone(),
465 other => ExecFailed {
466 kind: ExecFailureKind::Other,
467 errno: None,
468 errno_name: None,
469 message: other.to_string(),
470 stage: None,
471 },
472 };
473 let reply = Message::with_payload(MessageType::ExecFailed, msg.id, &payload)
474 .map_err(|e| AgentdError::ExecSession(format!("encode failed: {e}")))?;
475 codec::encode_to_buf(&reply, out_buf).map_err(|e| {
476 AgentdError::ExecSession(format!("encode failed frame: {e}"))
477 })?;
478 eprintln!("failed to spawn exec session {}: {e}", msg.id);
479 }
480 }
481 }
482
483 MessageType::ExecStdin => {
484 let Some(stdin) = decode_payload_or_core_error::<ExecStdin>(&msg, out_buf)? else {
485 return Ok(());
486 };
487 if let Some(session) = state.sessions.get_mut(&msg.id) {
488 if stdin.data.is_empty() {
489 session.close_stdin();
491 } else if let Err(e) = session.write_stdin(&stdin.data).await {
492 let payload = stdin_error_payload(&e);
493 eprintln!("stdin write error on session {}: {e}", msg.id);
494 let reply =
495 Message::with_payload(MessageType::ExecStdinError, msg.id, &payload)
496 .map_err(|e| {
497 AgentdError::ExecSession(format!("encode stdin error: {e}"))
498 })?;
499 codec::encode_to_buf(&reply, out_buf).map_err(|e| {
500 AgentdError::ExecSession(format!("encode stdin error frame: {e}"))
501 })?;
502 }
503 }
504 }
505
506 MessageType::ExecResize => {
507 let Some(resize) = decode_payload_or_core_error::<ExecResize>(&msg, out_buf)? else {
508 return Ok(());
509 };
510 if let Some(session) = state.sessions.get(&msg.id) {
511 let _ = session.resize(resize.rows, resize.cols);
512 }
513 }
514
515 MessageType::ExecSignal => {
516 let Some(signal) = decode_payload_or_core_error::<ExecSignal>(&msg, out_buf)? else {
517 return Ok(());
518 };
519 if let Some(session) = state.sessions.get(&msg.id) {
520 let _ = session.send_signal(signal.signal);
521 }
522 }
523
524 MessageType::FsRequest => {
525 let Some(req) = decode_payload_or_core_error::<FsRequest>(&msg, out_buf)? else {
526 return Ok(());
527 };
528 match fs::handle_fs_request(msg.id, req, &mut state.fs, out_buf, session_tx).await {
529 Ok(Some(FsStreamSession::Read(rs))) => {
530 state.read_sessions.insert(msg.id, rs);
531 }
532 Ok(Some(FsStreamSession::Write(ws))) => {
533 state.write_sessions.insert(msg.id, ws);
534 }
535 Ok(None) => {}
536 Err(e) => {
537 eprintln!("fs request error for {}: {e}", msg.id);
538 }
539 }
540 }
541
542 MessageType::FsData => {
543 let Some(data) = decode_payload_or_core_error::<FsData>(&msg, out_buf)? else {
544 return Ok(());
545 };
546 let len = data.data.len();
547 if let Some(session) = state.write_sessions.get_mut(&msg.id) {
548 match fs::handle_fs_data(msg.id, data, session, out_buf).await {
549 Ok(true) => {
550 state.write_sessions.remove(&msg.id);
552 }
553 Ok(false) => {
554 activity.add_fs_bytes(len);
555 }
556 Err(e) => {
557 eprintln!("fs data error for {}: {e}", msg.id);
558 state.write_sessions.remove(&msg.id);
559 }
560 }
561 } else {
562 let resp = microsandbox_protocol::fs::FsResponse {
564 ok: false,
565 error: Some(format!("unknown write session: {}", msg.id)),
566 data: None,
567 };
568 let reply = Message::with_payload(MessageType::FsResponse, msg.id, &resp)
569 .map_err(|e| AgentdError::ExecSession(format!("encode fs error: {e}")))?;
570 codec::encode_to_buf(&reply, out_buf)
571 .map_err(|e| AgentdError::ExecSession(format!("encode fs error frame: {e}")))?;
572 }
573 }
574
575 MessageType::TcpConnect => {
576 let Some(req) = decode_payload_or_core_error::<TcpConnect>(&msg, out_buf)? else {
577 return Ok(());
578 };
579 let session = TcpSession::open(msg.id, req, session_tx);
582 state.tcp_sessions.insert(msg.id, session);
583 }
584
585 MessageType::TcpData => {
586 let Some(data) = decode_payload_or_core_error::<TcpData>(&msg, out_buf)? else {
587 return Ok(());
588 };
589 let len = data.data.len();
590 if let Some(session) = state.tcp_sessions.get(&msg.id) {
591 if let Err(e) = session.write_data(data.data).await {
592 state.tcp_sessions.remove(&msg.id);
593 encode_tcp_failed(msg.id, e, out_buf)?;
594 } else {
595 activity.add_tcp_bytes(len);
596 }
597 } else {
598 encode_tcp_failed(msg.id, format!("unknown TCP session: {}", msg.id), out_buf)?;
599 }
600 }
601
602 MessageType::TcpEof => {
603 let Some(_) = decode_payload_or_core_error::<TcpEof>(&msg, out_buf)? else {
604 return Ok(());
605 };
606 if let Some(session) = state.tcp_sessions.get(&msg.id)
607 && let Err(e) = session.close_write().await
608 {
609 state.tcp_sessions.remove(&msg.id);
610 encode_tcp_failed(msg.id, e, out_buf)?;
611 }
612 }
613
614 MessageType::TcpClose => {
615 let Some(_) = decode_payload_or_core_error::<TcpClose>(&msg, out_buf)? else {
616 return Ok(());
617 };
618 if let Some(session) = state.tcp_sessions.remove(&msg.id) {
619 session.close();
620 }
621 }
622
623 MessageType::RelayClientDisconnected => {
624 let Some(disconnected) =
625 decode_payload_or_core_error::<RelayClientDisconnected>(&msg, out_buf)?
626 else {
627 return Ok(());
628 };
629 state
630 .fs
631 .close_owner_range(disconnected.id_start, disconnected.id_end_exclusive);
632 abort_read_sessions_in_owner_range(
633 &mut state.read_sessions,
634 disconnected.id_start,
635 disconnected.id_end_exclusive,
636 );
637 state.write_sessions.retain(|_, session| {
638 let owner_id = session.owner_id();
639 owner_id < disconnected.id_start || owner_id >= disconnected.id_end_exclusive
640 });
641 close_tcp_sessions_in_owner_range(
642 &mut state.tcp_sessions,
643 disconnected.id_start,
644 disconnected.id_end_exclusive,
645 );
646 }
647
648 MessageType::ClockSync => {
649 let Some(sync) = decode_payload_or_core_error::<ClockSync>(&msg, out_buf)? else {
650 return Ok(());
651 };
652 if let Err(e) = clock::sync_realtime_unix_nanos(sync.unix_time_nanos) {
653 eprintln!("clock: failed to sync realtime clock: {e}");
654 }
655 }
656
657 MessageType::Shutdown => {
658 for (_, session) in state.sessions.drain() {
662 let _ = session.send_signal(15); }
664 state.write_sessions.clear();
665 for (_, session) in state.tcp_sessions.drain() {
666 session.close();
667 }
668 state.fs.clear();
669
670 request_guest_poweroff()?;
671 return Err(AgentdError::Shutdown);
672 }
673
674 _ => {
675 }
677 }
678
679 Ok(())
680}
681
682const DEFAULT_GUEST_PATH: &str = "/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin";
688
689fn message_refreshes_idle_timer(t: &MessageType) -> bool {
696 !matches!(
697 t,
698 MessageType::ClockSync | MessageType::Ping | MessageType::Touch
699 )
700}
701
702fn guest_message_refreshes_idle_timer(t: &MessageType) -> bool {
710 !matches!(
711 t,
712 MessageType::Pong | MessageType::Touched | MessageType::CoreError
713 )
714}
715
716fn spawn_heartbeat_thread(
726 snapshot_rx: watch::Receiver<HeartbeatSnapshot>,
727 shutdown: Arc<AtomicBool>,
728) -> std::thread::JoinHandle<()> {
729 std::thread::Builder::new()
730 .name("agentd-heartbeat".to_string())
731 .spawn(move || {
732 let mut heartbeat_seq = 0u64;
733 let mut last_activity_seq = snapshot_rx.borrow().activity_seq;
734 let mut last_activity = Utc::now();
735
736 let interval = Duration::from_secs(HEARTBEAT_INTERVAL_SECS);
737 let step = Duration::from_millis(100);
738
739 while !shutdown.load(Ordering::Relaxed) {
740 let mut slept = Duration::ZERO;
741 while slept < interval {
742 if shutdown.load(Ordering::Relaxed) {
743 return;
744 }
745 std::thread::sleep(step);
746 slept += step;
747 }
748
749 if !heartbeat::heartbeat_dir_exists() {
750 continue;
751 }
752
753 heartbeat_seq = heartbeat_seq.saturating_add(1);
754 let snapshot = snapshot_rx.borrow().clone();
755 let timestamp = Utc::now();
756 if snapshot.activity_seq != last_activity_seq {
757 last_activity_seq = snapshot.activity_seq;
758 last_activity = timestamp;
759 }
760 let heartbeat = Heartbeat {
761 heartbeat_seq,
762 activity_seq: snapshot.activity_seq,
763 timestamp,
764 last_activity,
765 active_exec_sessions: snapshot.active_exec_sessions,
766 active_fs_streams: snapshot.active_fs_streams,
767 active_tcp_streams: snapshot.active_tcp_streams,
768 activity_counters: snapshot.counters,
769 };
770 let _ = heartbeat::write_heartbeat(&heartbeat);
771 }
772 })
773 .expect("failed to spawn agentd heartbeat thread")
774}
775
776fn heartbeat_snapshot(state: &AgentState, activity: &ActivityTracker) -> HeartbeatSnapshot {
777 HeartbeatSnapshot {
778 activity_seq: activity.activity_seq,
779 active_exec_sessions: state.sessions.len() as u32,
780 active_fs_streams: state
781 .read_sessions
782 .len()
783 .saturating_add(state.write_sessions.len()) as u32,
784 active_tcp_streams: state.tcp_sessions.len() as u32,
785 counters: activity.counters,
786 }
787}
788
789fn publish_heartbeat_snapshot(
790 heartbeat_tx: &watch::Sender<HeartbeatSnapshot>,
791 state: &AgentState,
792 activity: &ActivityTracker,
793) {
794 let _ = heartbeat_tx.send(heartbeat_snapshot(state, activity));
795}
796
797fn record_encoded_guest_messages(out_buf: &[u8], start: usize, activity: &mut ActivityTracker) {
798 let mut offset = start;
799 while offset + 4 <= out_buf.len() {
800 let frame_len = u32::from_be_bytes([
801 out_buf[offset],
802 out_buf[offset + 1],
803 out_buf[offset + 2],
804 out_buf[offset + 3],
805 ]) as usize;
806 let total = 4usize.saturating_add(frame_len);
807 if offset.saturating_add(total) > out_buf.len() {
808 break;
809 }
810
811 if encoded_guest_message_refreshes_idle_timer(out_buf, offset, frame_len) {
812 activity.record_guest_message();
813 }
814 offset += total;
815 }
816}
817
818fn encoded_guest_message_refreshes_idle_timer(
819 out_buf: &[u8],
820 offset: usize,
821 frame_len: usize,
822) -> bool {
823 if frame_len < microsandbox_protocol::message::FRAME_HEADER_SIZE {
824 return true;
825 }
826
827 let id_start = offset + 4;
828 let flags_index = id_start + 4;
829 let body_start = flags_index + 1;
830 let body_end = offset + 4 + frame_len;
831 if body_end > out_buf.len() || body_start > body_end {
832 return true;
833 }
834
835 let id = u32::from_be_bytes([
836 out_buf[id_start],
837 out_buf[id_start + 1],
838 out_buf[id_start + 2],
839 out_buf[id_start + 3],
840 ]);
841 let frame = codec::RawFrame {
842 id,
843 flags: out_buf[flags_index],
844 body: out_buf[body_start..body_end].to_vec(),
845 };
846
847 codec::raw_frame_to_message(frame)
848 .map(|msg| guest_message_refreshes_idle_timer(&msg.t))
849 .unwrap_or(true)
850}
851
852fn apply_raw_activity(raw: RawActivity, activity: &mut ActivityTracker) {
853 if raw.guest_message {
854 activity.record_guest_message();
855 }
856 if raw.fs_bytes > 0 {
857 activity.add_fs_bytes(raw.fs_bytes);
858 }
859 if raw.tcp_bytes > 0 {
860 activity.add_tcp_bytes(raw.tcp_bytes);
861 }
862}
863
864fn complete_raw_session(
865 id: u32,
866 completion: Option<RawSessionCompletion>,
867 read_sessions: &mut HashMap<u32, FsReadSession>,
868 tcp_sessions: &mut HashMap<u32, TcpSession>,
869) {
870 match completion {
871 Some(RawSessionCompletion::FsRead) => {
872 read_sessions.remove(&id);
873 }
874 Some(RawSessionCompletion::Tcp) => {
875 tcp_sessions.remove(&id);
876 }
877 None => {}
878 }
879}
880
881fn abort_read_sessions_in_owner_range(
882 read_sessions: &mut HashMap<u32, FsReadSession>,
883 id_start: u32,
884 id_end_exclusive: u32,
885) {
886 let mut retained = HashMap::new();
887 for (id, session) in read_sessions.drain() {
888 let owner_id = session.owner_id();
889 if owner_id >= id_start && owner_id < id_end_exclusive {
890 session.abort();
891 } else {
892 retained.insert(id, session);
893 }
894 }
895 *read_sessions = retained;
896}
897
898fn close_tcp_sessions_in_owner_range(
899 tcp_sessions: &mut HashMap<u32, TcpSession>,
900 id_start: u32,
901 id_end_exclusive: u32,
902) {
903 let mut retained = HashMap::new();
904 for (id, session) in tcp_sessions.drain() {
905 let owner_id = session.owner_id();
906 if owner_id >= id_start && owner_id < id_end_exclusive {
907 session.close();
908 } else {
909 retained.insert(id, session);
910 }
911 }
912 *tcp_sessions = retained;
913}
914
915fn encode_tcp_failed(id: u32, error: String, out_buf: &mut Vec<u8>) -> AgentdResult<()> {
916 let reply = Message::with_payload(MessageType::TcpFailed, id, &TcpFailed { error })
917 .map_err(|e| AgentdError::ExecSession(format!("encode tcp failed: {e}")))?;
918 codec::encode_to_buf(&reply, out_buf)
919 .map_err(|e| AgentdError::ExecSession(format!("encode tcp failed frame: {e}")))?;
920 Ok(())
921}
922
923fn encode_core_error_if_supported(
924 source: &Message,
925 id: u32,
926 kind: CoreErrorKind,
927 message: String,
928 offending_type: Option<String>,
929 out_buf: &mut Vec<u8>,
930) -> AgentdResult<()> {
931 if !MessageType::CoreError.is_available_at(source.v) {
932 return Err(AgentdError::ExecSession(format!(
933 "cannot send core.error to protocol generation {}",
934 source.v
935 )));
936 }
937
938 encode_core_error(id, kind, message, offending_type, out_buf)
939}
940
941fn encode_core_error(
942 id: u32,
943 kind: CoreErrorKind,
944 message: String,
945 offending_type: Option<String>,
946 out_buf: &mut Vec<u8>,
947) -> AgentdResult<()> {
948 let reply = Message::with_payload(
949 MessageType::CoreError,
950 id,
951 &CoreError {
952 kind,
953 message,
954 offending_type,
955 },
956 )
957 .map_err(|e| AgentdError::ExecSession(format!("encode core error: {e}")))?;
958 codec::encode_to_buf(&reply, out_buf)
959 .map_err(|e| AgentdError::ExecSession(format!("encode core error frame: {e}")))?;
960 Ok(())
961}
962
963fn decode_payload_or_core_error<T>(msg: &Message, out_buf: &mut Vec<u8>) -> AgentdResult<Option<T>>
964where
965 T: serde::de::DeserializeOwned,
966{
967 match msg.payload::<T>() {
968 Ok(payload) => Ok(Some(payload)),
969 Err(error) => {
970 encode_core_error_if_supported(
971 msg,
972 msg.id,
973 CoreErrorKind::InvalidPayload,
974 format!("decode payload for {}: {error}", msg.t.as_str()),
975 Some(msg.t.as_str().to_string()),
976 out_buf,
977 )?;
978 Ok(None)
979 }
980 }
981}
982
983fn stdin_error_payload(err: &AgentdError) -> ExecStdinError {
985 let io_err = match err {
986 AgentdError::Io(e) => Some(e),
987 _ => None,
988 };
989 let errno = io_err.and_then(|e| e.raw_os_error());
990 ExecStdinError {
991 errno,
992 errno_name: errno.and_then(errno_name),
993 message: err.to_string(),
994 }
995}
996
997fn errno_name(code: i32) -> Option<String> {
1000 let name = match code {
1001 libc::EPIPE => "EPIPE",
1002 libc::EBADF => "EBADF",
1003 libc::EINVAL => "EINVAL",
1004 libc::EIO => "EIO",
1005 libc::ENOSPC => "ENOSPC",
1006 libc::EFBIG => "EFBIG",
1007 _ => return None,
1008 };
1009 Some(name.to_string())
1010}
1011
1012fn prepend_scripts_to_path(req: &mut microsandbox_protocol::exec::ExecRequest) {
1013 let scripts = microsandbox_protocol::SCRIPTS_PATH;
1014
1015 if let Some(entry) = req.env.iter_mut().find(|e| e.starts_with("PATH=")) {
1017 let existing = &entry["PATH=".len()..];
1018 *entry = format!("PATH={scripts}:{existing}");
1019 } else {
1020 let inherited = env::var("PATH").unwrap_or_else(|_| DEFAULT_GUEST_PATH.to_string());
1023 req.env.push(format!("PATH={scripts}:{inherited}"));
1024 }
1025}
1026
1027fn set_nonblocking(fd: i32) -> AgentdResult<()> {
1029 let flags = unsafe { libc::fcntl(fd, libc::F_GETFL) };
1030 if flags < 0 {
1031 return Err(std::io::Error::last_os_error().into());
1032 }
1033 let ret = unsafe { libc::fcntl(fd, libc::F_SETFL, flags | libc::O_NONBLOCK) };
1034 if ret < 0 {
1035 return Err(std::io::Error::last_os_error().into());
1036 }
1037 Ok(())
1038}
1039
1040fn init_ack_deadline() -> Instant {
1041 Instant::now() + std::time::Duration::from_secs(INIT_ACK_TIMEOUT_SECS)
1042}
1043
1044fn init_ack_timeout() -> AgentdError {
1045 AgentdError::ExecSession("timed out waiting for init ack".into())
1046}
1047
1048fn wait_for_init_ack(fd: i32, deadline: Instant) -> AgentdResult<()> {
1049 let mut serial_in_buf = Vec::new();
1050 let mut read_buf = [0u8; 4096];
1051
1052 loop {
1053 if let Some(msg) = codec::try_decode_from_buf(&mut serial_in_buf)
1054 .map_err(|e| AgentdError::ExecSession(format!("decode init ack: {e}")))?
1055 {
1056 if msg.t == MessageType::InitAck {
1057 let _: InitAck = msg.payload().map_err(|e| {
1058 AgentdError::ExecSession(format!("decode init ack payload: {e}"))
1059 })?;
1060 return Ok(());
1061 }
1062
1063 return Err(AgentdError::ExecSession(format!(
1064 "expected core.init.ack, got {}",
1065 msg.t.as_str()
1066 )));
1067 }
1068
1069 if serial_in_buf.len() > MAX_INPUT_BUF_SIZE {
1070 return Err(AgentdError::ExecSession(
1071 "serial input buffer exceeded maximum size while waiting for init ack".into(),
1072 ));
1073 }
1074
1075 if !poll_fd_until(fd, libc::POLLIN, deadline)? {
1076 return Err(init_ack_timeout());
1077 }
1078
1079 let n = match read_from_fd(fd, &mut read_buf) {
1080 Ok(n) => n,
1081 Err(e)
1082 if matches!(
1083 e.kind(),
1084 std::io::ErrorKind::Interrupted | std::io::ErrorKind::WouldBlock
1085 ) =>
1086 {
1087 continue;
1088 }
1089 Err(e) => return Err(e.into()),
1090 };
1091 if n == 0 {
1092 return Err(AgentdError::ExecSession(
1093 "serial port closed while waiting for init ack".into(),
1094 ));
1095 }
1096 serial_in_buf.extend_from_slice(&read_buf[..n]);
1097 }
1098}
1099
1100fn poll_fd_until(fd: i32, events: i16, deadline: Instant) -> AgentdResult<bool> {
1101 loop {
1102 let remaining = deadline.saturating_duration_since(Instant::now());
1103 if remaining.is_zero() {
1104 return Ok(false);
1105 }
1106
1107 let timeout_ms = remaining.as_millis().min(i32::MAX as u128) as i32;
1108 let timeout_ms = if timeout_ms == 0 { 1 } else { timeout_ms };
1109 let mut pfd = libc::pollfd {
1110 fd,
1111 events,
1112 revents: 0,
1113 };
1114 let ret = unsafe { libc::poll(&mut pfd, 1, timeout_ms) };
1115 if ret > 0 {
1116 return Ok(true);
1117 }
1118 if ret == 0 {
1119 return Ok(false);
1120 }
1121 let err = std::io::Error::last_os_error();
1122 if err.raw_os_error() == Some(libc::EINTR) {
1123 continue;
1124 }
1125 return Err(err.into());
1126 }
1127}
1128
1129fn read_from_fd(fd: i32, buf: &mut [u8]) -> std::io::Result<usize> {
1131 let n = unsafe { libc::read(fd, buf.as_mut_ptr() as *mut libc::c_void, buf.len()) };
1132 if n < 0 {
1133 Err(std::io::Error::last_os_error())
1134 } else {
1135 Ok(n as usize)
1136 }
1137}
1138
1139fn write_all_to_fd(fd: i32, mut buf: &[u8], deadline: Instant) -> AgentdResult<()> {
1140 while !buf.is_empty() {
1141 match write_to_fd(fd, buf) {
1142 Ok(0) => return Err(std::io::Error::from(std::io::ErrorKind::WriteZero).into()),
1143 Ok(n) => buf = &buf[n..],
1144 Err(e) if e.kind() == std::io::ErrorKind::Interrupted => continue,
1145 Err(e) if e.kind() == std::io::ErrorKind::WouldBlock => {
1146 if !poll_fd_until(fd, libc::POLLOUT, deadline)? {
1147 return Err(init_ack_timeout());
1148 }
1149 }
1150 Err(e) => return Err(e.into()),
1151 }
1152 }
1153
1154 Ok(())
1155}
1156
1157async fn flush_write_buf(fd: &AsyncFd<std::fs::File>, buf: &mut Vec<u8>) -> AgentdResult<()> {
1159 while !buf.is_empty() {
1160 let mut guard = fd.writable().await?;
1161 match guard.try_io(|inner| write_to_fd(inner.get_ref().as_raw_fd(), buf)) {
1162 Ok(Ok(n)) => {
1163 buf.drain(..n);
1164 }
1165 Ok(Err(e)) if e.kind() == std::io::ErrorKind::Interrupted => continue,
1166 Ok(Err(e)) => return Err(e.into()),
1167 Err(_would_block) => continue,
1168 }
1169 }
1170 Ok(())
1171}
1172
1173fn write_to_fd(fd: i32, buf: &[u8]) -> std::io::Result<usize> {
1175 let n = unsafe { libc::write(fd, buf.as_ptr() as *const libc::c_void, buf.len()) };
1176 if n < 0 {
1177 Err(std::io::Error::last_os_error())
1178 } else {
1179 Ok(n as usize)
1180 }
1181}
1182
1183fn request_guest_poweroff() -> AgentdResult<()> {
1184 if crate::handoff::is_pid_1() {
1185 crate::teardown::teardown_filesystems(true);
1188 let ret = unsafe { libc::reboot(libc::RB_POWER_OFF) };
1189 if ret != 0 {
1190 return Err(std::io::Error::last_os_error().into());
1191 }
1192 return Ok(());
1193 }
1194
1195 unsafe {
1196 libc::sync();
1197 }
1198
1199 if crate::handoff::signal_init_shutdown().is_ok() {
1205 std::thread::sleep(HANDOFF_POWEROFF_TIMEOUT);
1206 }
1207
1208 crate::teardown::teardown_filesystems(false);
1214
1215 let _ = crate::handoff::signal_init_term();
1216 Ok(())
1217}
1218
1219#[cfg(test)]
1224mod tests {
1225 use super::*;
1226
1227 #[test]
1228 fn record_encoded_guest_messages_counts_only_appended_frames() {
1229 let mut out_buf = Vec::new();
1230 let existing =
1231 Message::with_payload(MessageType::ExecStarted, 1, &ExecStarted { pid: 123 }).unwrap();
1232 codec::encode_to_buf(&existing, &mut out_buf).unwrap();
1233 let start = out_buf.len();
1234
1235 let appended =
1236 Message::with_payload(MessageType::ExecStarted, 2, &ExecStarted { pid: 456 }).unwrap();
1237 codec::encode_to_buf(&appended, &mut out_buf).unwrap();
1238
1239 let mut activity = ActivityTracker::new();
1240 record_encoded_guest_messages(&out_buf, start, &mut activity);
1241
1242 assert_eq!(activity.activity_seq, 1);
1243 assert_eq!(activity.counters.guest_messages, 1);
1244 }
1245
1246 #[test]
1247 fn apply_raw_activity_updates_guest_and_byte_counters() {
1248 let mut activity = ActivityTracker::new();
1249
1250 apply_raw_activity(RawActivity::fs_bytes(42), &mut activity);
1251 apply_raw_activity(RawActivity::tcp_bytes(7), &mut activity);
1252
1253 assert_eq!(activity.activity_seq, 2);
1254 assert_eq!(activity.counters.guest_messages, 2);
1255 assert_eq!(activity.counters.fs_bytes, 42);
1256 assert_eq!(activity.counters.tcp_bytes, 7);
1257 }
1258
1259 #[test]
1260 fn maintenance_messages_do_not_implicitly_refresh_idle_timer() {
1261 assert!(!message_refreshes_idle_timer(&MessageType::ClockSync));
1262 assert!(!message_refreshes_idle_timer(&MessageType::Ping));
1263 assert!(!message_refreshes_idle_timer(&MessageType::Touch));
1264 assert!(message_refreshes_idle_timer(&MessageType::ExecRequest));
1265 }
1266
1267 #[test]
1268 fn maintenance_replies_do_not_refresh_idle_timer() {
1269 assert!(!guest_message_refreshes_idle_timer(&MessageType::Pong));
1270 assert!(!guest_message_refreshes_idle_timer(&MessageType::Touched));
1271 assert!(!guest_message_refreshes_idle_timer(&MessageType::CoreError));
1272 assert!(guest_message_refreshes_idle_timer(&MessageType::ExecStdout));
1273 }
1274
1275 #[test]
1276 fn record_encoded_guest_messages_ignores_pong_and_touched() {
1277 let mut out_buf = Vec::new();
1278 let pong = Message::with_payload(MessageType::Pong, 1, &Pong {}).unwrap();
1279 codec::encode_to_buf(&pong, &mut out_buf).unwrap();
1280
1281 let touched =
1282 Message::with_payload(MessageType::Touched, 2, &Touched { activity_seq: 42 }).unwrap();
1283 codec::encode_to_buf(&touched, &mut out_buf).unwrap();
1284
1285 let mut activity = ActivityTracker::new();
1286 record_encoded_guest_messages(&out_buf, 0, &mut activity);
1287
1288 assert_eq!(activity.activity_seq, 0);
1289 assert_eq!(activity.counters.guest_messages, 0);
1290 }
1291}