1use std::collections::HashMap;
4use std::fs::{File, OpenOptions};
5use std::os::fd::AsRawFd;
6use std::sync::Arc;
7use std::sync::atomic::{AtomicBool, Ordering};
8use std::time::Instant;
9use std::{env, ptr};
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, Ready, RelayClientDisconnected,
20 ResolvedUser,
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::ExecRequest => {
407 let Some(mut req) = decode_payload_or_core_error::<ExecRequest>(&msg, out_buf)? else {
408 return Ok(());
409 };
410 prepend_scripts_to_path(&mut req);
411 match ExecSession::spawn(
412 msg.id,
413 &req,
414 session_tx.clone(),
415 config.user.as_deref(),
416 config.security_profile,
417 ) {
418 Ok(session) => {
419 let reply = Message::with_payload(
420 MessageType::ExecStarted,
421 msg.id,
422 &ExecStarted { pid: session.pid() },
423 )
424 .map_err(|e| AgentdError::ExecSession(format!("encode started: {e}")))?;
425 codec::encode_to_buf(&reply, out_buf).map_err(|e| {
426 AgentdError::ExecSession(format!("encode started frame: {e}"))
427 })?;
428 state.sessions.insert(msg.id, session);
429 }
430 Err(e) => {
431 let payload = match &e {
437 AgentdError::ExecSpawnFailed(p) => p.clone(),
438 other => ExecFailed {
439 kind: ExecFailureKind::Other,
440 errno: None,
441 errno_name: None,
442 message: other.to_string(),
443 stage: None,
444 },
445 };
446 let reply = Message::with_payload(MessageType::ExecFailed, msg.id, &payload)
447 .map_err(|e| AgentdError::ExecSession(format!("encode failed: {e}")))?;
448 codec::encode_to_buf(&reply, out_buf).map_err(|e| {
449 AgentdError::ExecSession(format!("encode failed frame: {e}"))
450 })?;
451 eprintln!("failed to spawn exec session {}: {e}", msg.id);
452 }
453 }
454 }
455
456 MessageType::ExecStdin => {
457 let Some(stdin) = decode_payload_or_core_error::<ExecStdin>(&msg, out_buf)? else {
458 return Ok(());
459 };
460 if let Some(session) = state.sessions.get_mut(&msg.id) {
461 if stdin.data.is_empty() {
462 session.close_stdin();
464 } else if let Err(e) = session.write_stdin(&stdin.data).await {
465 let payload = stdin_error_payload(&e);
466 eprintln!("stdin write error on session {}: {e}", msg.id);
467 let reply =
468 Message::with_payload(MessageType::ExecStdinError, msg.id, &payload)
469 .map_err(|e| {
470 AgentdError::ExecSession(format!("encode stdin error: {e}"))
471 })?;
472 codec::encode_to_buf(&reply, out_buf).map_err(|e| {
473 AgentdError::ExecSession(format!("encode stdin error frame: {e}"))
474 })?;
475 }
476 }
477 }
478
479 MessageType::ExecResize => {
480 let Some(resize) = decode_payload_or_core_error::<ExecResize>(&msg, out_buf)? else {
481 return Ok(());
482 };
483 if let Some(session) = state.sessions.get(&msg.id) {
484 let _ = session.resize(resize.rows, resize.cols);
485 }
486 }
487
488 MessageType::ExecSignal => {
489 let Some(signal) = decode_payload_or_core_error::<ExecSignal>(&msg, out_buf)? else {
490 return Ok(());
491 };
492 if let Some(session) = state.sessions.get(&msg.id) {
493 let _ = session.send_signal(signal.signal);
494 }
495 }
496
497 MessageType::FsRequest => {
498 let Some(req) = decode_payload_or_core_error::<FsRequest>(&msg, out_buf)? else {
499 return Ok(());
500 };
501 match fs::handle_fs_request(msg.id, req, &mut state.fs, out_buf, session_tx).await {
502 Ok(Some(FsStreamSession::Read(rs))) => {
503 state.read_sessions.insert(msg.id, rs);
504 }
505 Ok(Some(FsStreamSession::Write(ws))) => {
506 state.write_sessions.insert(msg.id, ws);
507 }
508 Ok(None) => {}
509 Err(e) => {
510 eprintln!("fs request error for {}: {e}", msg.id);
511 }
512 }
513 }
514
515 MessageType::FsData => {
516 let Some(data) = decode_payload_or_core_error::<FsData>(&msg, out_buf)? else {
517 return Ok(());
518 };
519 let len = data.data.len();
520 if let Some(session) = state.write_sessions.get_mut(&msg.id) {
521 match fs::handle_fs_data(msg.id, data, session, out_buf).await {
522 Ok(true) => {
523 state.write_sessions.remove(&msg.id);
525 }
526 Ok(false) => {
527 activity.add_fs_bytes(len);
528 }
529 Err(e) => {
530 eprintln!("fs data error for {}: {e}", msg.id);
531 state.write_sessions.remove(&msg.id);
532 }
533 }
534 } else {
535 let resp = microsandbox_protocol::fs::FsResponse {
537 ok: false,
538 error: Some(format!("unknown write session: {}", msg.id)),
539 data: None,
540 };
541 let reply = Message::with_payload(MessageType::FsResponse, msg.id, &resp)
542 .map_err(|e| AgentdError::ExecSession(format!("encode fs error: {e}")))?;
543 codec::encode_to_buf(&reply, out_buf)
544 .map_err(|e| AgentdError::ExecSession(format!("encode fs error frame: {e}")))?;
545 }
546 }
547
548 MessageType::TcpConnect => {
549 let Some(req) = decode_payload_or_core_error::<TcpConnect>(&msg, out_buf)? else {
550 return Ok(());
551 };
552 let session = TcpSession::open(msg.id, req, session_tx);
555 state.tcp_sessions.insert(msg.id, session);
556 }
557
558 MessageType::TcpData => {
559 let Some(data) = decode_payload_or_core_error::<TcpData>(&msg, out_buf)? else {
560 return Ok(());
561 };
562 let len = data.data.len();
563 if let Some(session) = state.tcp_sessions.get(&msg.id) {
564 if let Err(e) = session.write_data(data.data).await {
565 state.tcp_sessions.remove(&msg.id);
566 encode_tcp_failed(msg.id, e, out_buf)?;
567 } else {
568 activity.add_tcp_bytes(len);
569 }
570 } else {
571 encode_tcp_failed(msg.id, format!("unknown TCP session: {}", msg.id), out_buf)?;
572 }
573 }
574
575 MessageType::TcpEof => {
576 let Some(_) = decode_payload_or_core_error::<TcpEof>(&msg, out_buf)? else {
577 return Ok(());
578 };
579 if let Some(session) = state.tcp_sessions.get(&msg.id)
580 && let Err(e) = session.close_write().await
581 {
582 state.tcp_sessions.remove(&msg.id);
583 encode_tcp_failed(msg.id, e, out_buf)?;
584 }
585 }
586
587 MessageType::TcpClose => {
588 let Some(_) = decode_payload_or_core_error::<TcpClose>(&msg, out_buf)? else {
589 return Ok(());
590 };
591 if let Some(session) = state.tcp_sessions.remove(&msg.id) {
592 session.close();
593 }
594 }
595
596 MessageType::RelayClientDisconnected => {
597 let Some(disconnected) =
598 decode_payload_or_core_error::<RelayClientDisconnected>(&msg, out_buf)?
599 else {
600 return Ok(());
601 };
602 state
603 .fs
604 .close_owner_range(disconnected.id_start, disconnected.id_end_exclusive);
605 abort_read_sessions_in_owner_range(
606 &mut state.read_sessions,
607 disconnected.id_start,
608 disconnected.id_end_exclusive,
609 );
610 state.write_sessions.retain(|_, session| {
611 let owner_id = session.owner_id();
612 owner_id < disconnected.id_start || owner_id >= disconnected.id_end_exclusive
613 });
614 close_tcp_sessions_in_owner_range(
615 &mut state.tcp_sessions,
616 disconnected.id_start,
617 disconnected.id_end_exclusive,
618 );
619 }
620
621 MessageType::ClockSync => {
622 let Some(sync) = decode_payload_or_core_error::<ClockSync>(&msg, out_buf)? else {
623 return Ok(());
624 };
625 if let Err(e) = clock::sync_realtime_unix_nanos(sync.unix_time_nanos) {
626 eprintln!("clock: failed to sync realtime clock: {e}");
627 }
628 }
629
630 MessageType::Shutdown => {
631 for (_, session) in state.sessions.drain() {
635 let _ = session.send_signal(15); }
637 state.write_sessions.clear();
638 for (_, session) in state.tcp_sessions.drain() {
639 session.close();
640 }
641 state.fs.clear();
642
643 request_guest_poweroff()?;
644 return Err(AgentdError::Shutdown);
645 }
646
647 _ => {
648 }
650 }
651
652 Ok(())
653}
654
655const DEFAULT_GUEST_PATH: &str = "/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin";
661
662fn message_refreshes_idle_timer(t: &MessageType) -> bool {
667 !matches!(t, MessageType::ClockSync)
668}
669
670fn spawn_heartbeat_thread(
680 snapshot_rx: watch::Receiver<HeartbeatSnapshot>,
681 shutdown: Arc<AtomicBool>,
682) -> std::thread::JoinHandle<()> {
683 std::thread::Builder::new()
684 .name("agentd-heartbeat".to_string())
685 .spawn(move || {
686 let mut heartbeat_seq = 0u64;
687 let mut last_activity_seq = snapshot_rx.borrow().activity_seq;
688 let mut last_activity = Utc::now();
689
690 let interval = Duration::from_secs(HEARTBEAT_INTERVAL_SECS);
691 let step = Duration::from_millis(100);
692
693 while !shutdown.load(Ordering::Relaxed) {
694 let mut slept = Duration::ZERO;
695 while slept < interval {
696 if shutdown.load(Ordering::Relaxed) {
697 return;
698 }
699 std::thread::sleep(step);
700 slept += step;
701 }
702
703 if !heartbeat::heartbeat_dir_exists() {
704 continue;
705 }
706
707 heartbeat_seq = heartbeat_seq.saturating_add(1);
708 let snapshot = snapshot_rx.borrow().clone();
709 let timestamp = Utc::now();
710 if snapshot.activity_seq != last_activity_seq {
711 last_activity_seq = snapshot.activity_seq;
712 last_activity = timestamp;
713 }
714 let heartbeat = Heartbeat {
715 heartbeat_seq,
716 activity_seq: snapshot.activity_seq,
717 timestamp,
718 last_activity,
719 active_exec_sessions: snapshot.active_exec_sessions,
720 active_fs_streams: snapshot.active_fs_streams,
721 active_tcp_streams: snapshot.active_tcp_streams,
722 activity_counters: snapshot.counters,
723 };
724 let _ = heartbeat::write_heartbeat(&heartbeat);
725 }
726 })
727 .expect("failed to spawn agentd heartbeat thread")
728}
729
730fn heartbeat_snapshot(state: &AgentState, activity: &ActivityTracker) -> HeartbeatSnapshot {
731 HeartbeatSnapshot {
732 activity_seq: activity.activity_seq,
733 active_exec_sessions: state.sessions.len() as u32,
734 active_fs_streams: state
735 .read_sessions
736 .len()
737 .saturating_add(state.write_sessions.len()) as u32,
738 active_tcp_streams: state.tcp_sessions.len() as u32,
739 counters: activity.counters,
740 }
741}
742
743fn publish_heartbeat_snapshot(
744 heartbeat_tx: &watch::Sender<HeartbeatSnapshot>,
745 state: &AgentState,
746 activity: &ActivityTracker,
747) {
748 let _ = heartbeat_tx.send(heartbeat_snapshot(state, activity));
749}
750
751fn record_encoded_guest_messages(out_buf: &[u8], start: usize, activity: &mut ActivityTracker) {
752 let mut offset = start;
753 while offset + 4 <= out_buf.len() {
754 let frame_len = u32::from_be_bytes([
755 out_buf[offset],
756 out_buf[offset + 1],
757 out_buf[offset + 2],
758 out_buf[offset + 3],
759 ]) as usize;
760 let total = 4usize.saturating_add(frame_len);
761 if offset.saturating_add(total) > out_buf.len() {
762 break;
763 }
764
765 activity.record_guest_message();
766 offset += total;
767 }
768}
769
770fn apply_raw_activity(raw: RawActivity, activity: &mut ActivityTracker) {
771 if raw.guest_message {
772 activity.record_guest_message();
773 }
774 if raw.fs_bytes > 0 {
775 activity.add_fs_bytes(raw.fs_bytes);
776 }
777 if raw.tcp_bytes > 0 {
778 activity.add_tcp_bytes(raw.tcp_bytes);
779 }
780}
781
782fn complete_raw_session(
783 id: u32,
784 completion: Option<RawSessionCompletion>,
785 read_sessions: &mut HashMap<u32, FsReadSession>,
786 tcp_sessions: &mut HashMap<u32, TcpSession>,
787) {
788 match completion {
789 Some(RawSessionCompletion::FsRead) => {
790 read_sessions.remove(&id);
791 }
792 Some(RawSessionCompletion::Tcp) => {
793 tcp_sessions.remove(&id);
794 }
795 None => {}
796 }
797}
798
799fn abort_read_sessions_in_owner_range(
800 read_sessions: &mut HashMap<u32, FsReadSession>,
801 id_start: u32,
802 id_end_exclusive: u32,
803) {
804 let mut retained = HashMap::new();
805 for (id, session) in read_sessions.drain() {
806 let owner_id = session.owner_id();
807 if owner_id >= id_start && owner_id < id_end_exclusive {
808 session.abort();
809 } else {
810 retained.insert(id, session);
811 }
812 }
813 *read_sessions = retained;
814}
815
816fn close_tcp_sessions_in_owner_range(
817 tcp_sessions: &mut HashMap<u32, TcpSession>,
818 id_start: u32,
819 id_end_exclusive: u32,
820) {
821 let mut retained = HashMap::new();
822 for (id, session) in tcp_sessions.drain() {
823 let owner_id = session.owner_id();
824 if owner_id >= id_start && owner_id < id_end_exclusive {
825 session.close();
826 } else {
827 retained.insert(id, session);
828 }
829 }
830 *tcp_sessions = retained;
831}
832
833fn encode_tcp_failed(id: u32, error: String, out_buf: &mut Vec<u8>) -> AgentdResult<()> {
834 let reply = Message::with_payload(MessageType::TcpFailed, id, &TcpFailed { error })
835 .map_err(|e| AgentdError::ExecSession(format!("encode tcp failed: {e}")))?;
836 codec::encode_to_buf(&reply, out_buf)
837 .map_err(|e| AgentdError::ExecSession(format!("encode tcp failed frame: {e}")))?;
838 Ok(())
839}
840
841fn encode_core_error_if_supported(
842 source: &Message,
843 id: u32,
844 kind: CoreErrorKind,
845 message: String,
846 offending_type: Option<String>,
847 out_buf: &mut Vec<u8>,
848) -> AgentdResult<()> {
849 if !MessageType::CoreError.is_available_at(source.v) {
850 return Err(AgentdError::ExecSession(format!(
851 "cannot send core.error to protocol generation {}",
852 source.v
853 )));
854 }
855
856 encode_core_error(id, kind, message, offending_type, out_buf)
857}
858
859fn encode_core_error(
860 id: u32,
861 kind: CoreErrorKind,
862 message: String,
863 offending_type: Option<String>,
864 out_buf: &mut Vec<u8>,
865) -> AgentdResult<()> {
866 let reply = Message::with_payload(
867 MessageType::CoreError,
868 id,
869 &CoreError {
870 kind,
871 message,
872 offending_type,
873 },
874 )
875 .map_err(|e| AgentdError::ExecSession(format!("encode core error: {e}")))?;
876 codec::encode_to_buf(&reply, out_buf)
877 .map_err(|e| AgentdError::ExecSession(format!("encode core error frame: {e}")))?;
878 Ok(())
879}
880
881fn decode_payload_or_core_error<T>(msg: &Message, out_buf: &mut Vec<u8>) -> AgentdResult<Option<T>>
882where
883 T: serde::de::DeserializeOwned,
884{
885 match msg.payload::<T>() {
886 Ok(payload) => Ok(Some(payload)),
887 Err(error) => {
888 encode_core_error_if_supported(
889 msg,
890 msg.id,
891 CoreErrorKind::InvalidPayload,
892 format!("decode payload for {}: {error}", msg.t.as_str()),
893 Some(msg.t.as_str().to_string()),
894 out_buf,
895 )?;
896 Ok(None)
897 }
898 }
899}
900
901fn stdin_error_payload(err: &AgentdError) -> ExecStdinError {
903 let io_err = match err {
904 AgentdError::Io(e) => Some(e),
905 _ => None,
906 };
907 let errno = io_err.and_then(|e| e.raw_os_error());
908 ExecStdinError {
909 errno,
910 errno_name: errno.and_then(errno_name),
911 message: err.to_string(),
912 }
913}
914
915fn errno_name(code: i32) -> Option<String> {
918 let name = match code {
919 libc::EPIPE => "EPIPE",
920 libc::EBADF => "EBADF",
921 libc::EINVAL => "EINVAL",
922 libc::EIO => "EIO",
923 libc::ENOSPC => "ENOSPC",
924 libc::EFBIG => "EFBIG",
925 _ => return None,
926 };
927 Some(name.to_string())
928}
929
930fn prepend_scripts_to_path(req: &mut microsandbox_protocol::exec::ExecRequest) {
931 let scripts = microsandbox_protocol::SCRIPTS_PATH;
932
933 if let Some(entry) = req.env.iter_mut().find(|e| e.starts_with("PATH=")) {
935 let existing = &entry["PATH=".len()..];
936 *entry = format!("PATH={scripts}:{existing}");
937 } else {
938 let inherited = env::var("PATH").unwrap_or_else(|_| DEFAULT_GUEST_PATH.to_string());
941 req.env.push(format!("PATH={scripts}:{inherited}"));
942 }
943}
944
945fn set_nonblocking(fd: i32) -> AgentdResult<()> {
947 let flags = unsafe { libc::fcntl(fd, libc::F_GETFL) };
948 if flags < 0 {
949 return Err(std::io::Error::last_os_error().into());
950 }
951 let ret = unsafe { libc::fcntl(fd, libc::F_SETFL, flags | libc::O_NONBLOCK) };
952 if ret < 0 {
953 return Err(std::io::Error::last_os_error().into());
954 }
955 Ok(())
956}
957
958fn init_ack_deadline() -> Instant {
959 Instant::now() + std::time::Duration::from_secs(INIT_ACK_TIMEOUT_SECS)
960}
961
962fn init_ack_timeout() -> AgentdError {
963 AgentdError::ExecSession("timed out waiting for init ack".into())
964}
965
966fn wait_for_init_ack(fd: i32, deadline: Instant) -> AgentdResult<()> {
967 let mut serial_in_buf = Vec::new();
968 let mut read_buf = [0u8; 4096];
969
970 loop {
971 if let Some(msg) = codec::try_decode_from_buf(&mut serial_in_buf)
972 .map_err(|e| AgentdError::ExecSession(format!("decode init ack: {e}")))?
973 {
974 if msg.t == MessageType::InitAck {
975 let _: InitAck = msg.payload().map_err(|e| {
976 AgentdError::ExecSession(format!("decode init ack payload: {e}"))
977 })?;
978 return Ok(());
979 }
980
981 return Err(AgentdError::ExecSession(format!(
982 "expected core.init.ack, got {}",
983 msg.t.as_str()
984 )));
985 }
986
987 if serial_in_buf.len() > MAX_INPUT_BUF_SIZE {
988 return Err(AgentdError::ExecSession(
989 "serial input buffer exceeded maximum size while waiting for init ack".into(),
990 ));
991 }
992
993 if !poll_fd_until(fd, libc::POLLIN, deadline)? {
994 return Err(init_ack_timeout());
995 }
996
997 let n = match read_from_fd(fd, &mut read_buf) {
998 Ok(n) => n,
999 Err(e)
1000 if matches!(
1001 e.kind(),
1002 std::io::ErrorKind::Interrupted | std::io::ErrorKind::WouldBlock
1003 ) =>
1004 {
1005 continue;
1006 }
1007 Err(e) => return Err(e.into()),
1008 };
1009 if n == 0 {
1010 return Err(AgentdError::ExecSession(
1011 "serial port closed while waiting for init ack".into(),
1012 ));
1013 }
1014 serial_in_buf.extend_from_slice(&read_buf[..n]);
1015 }
1016}
1017
1018fn poll_fd_until(fd: i32, events: i16, deadline: Instant) -> AgentdResult<bool> {
1019 loop {
1020 let remaining = deadline.saturating_duration_since(Instant::now());
1021 if remaining.is_zero() {
1022 return Ok(false);
1023 }
1024
1025 let timeout_ms = remaining.as_millis().min(i32::MAX as u128) as i32;
1026 let timeout_ms = if timeout_ms == 0 { 1 } else { timeout_ms };
1027 let mut pfd = libc::pollfd {
1028 fd,
1029 events,
1030 revents: 0,
1031 };
1032 let ret = unsafe { libc::poll(&mut pfd, 1, timeout_ms) };
1033 if ret > 0 {
1034 return Ok(true);
1035 }
1036 if ret == 0 {
1037 return Ok(false);
1038 }
1039 let err = std::io::Error::last_os_error();
1040 if err.raw_os_error() == Some(libc::EINTR) {
1041 continue;
1042 }
1043 return Err(err.into());
1044 }
1045}
1046
1047fn read_from_fd(fd: i32, buf: &mut [u8]) -> std::io::Result<usize> {
1049 let n = unsafe { libc::read(fd, buf.as_mut_ptr() as *mut libc::c_void, buf.len()) };
1050 if n < 0 {
1051 Err(std::io::Error::last_os_error())
1052 } else {
1053 Ok(n as usize)
1054 }
1055}
1056
1057fn write_all_to_fd(fd: i32, mut buf: &[u8], deadline: Instant) -> AgentdResult<()> {
1058 while !buf.is_empty() {
1059 match write_to_fd(fd, buf) {
1060 Ok(0) => return Err(std::io::Error::from(std::io::ErrorKind::WriteZero).into()),
1061 Ok(n) => buf = &buf[n..],
1062 Err(e) if e.kind() == std::io::ErrorKind::Interrupted => continue,
1063 Err(e) if e.kind() == std::io::ErrorKind::WouldBlock => {
1064 if !poll_fd_until(fd, libc::POLLOUT, deadline)? {
1065 return Err(init_ack_timeout());
1066 }
1067 }
1068 Err(e) => return Err(e.into()),
1069 }
1070 }
1071
1072 Ok(())
1073}
1074
1075async fn flush_write_buf(fd: &AsyncFd<std::fs::File>, buf: &mut Vec<u8>) -> AgentdResult<()> {
1077 while !buf.is_empty() {
1078 let mut guard = fd.writable().await?;
1079 match guard.try_io(|inner| write_to_fd(inner.get_ref().as_raw_fd(), buf)) {
1080 Ok(Ok(n)) => {
1081 buf.drain(..n);
1082 }
1083 Ok(Err(e)) if e.kind() == std::io::ErrorKind::Interrupted => continue,
1084 Ok(Err(e)) => return Err(e.into()),
1085 Err(_would_block) => continue,
1086 }
1087 }
1088 Ok(())
1089}
1090
1091fn write_to_fd(fd: i32, buf: &[u8]) -> std::io::Result<usize> {
1093 let n = unsafe { libc::write(fd, buf.as_ptr() as *const libc::c_void, buf.len()) };
1094 if n < 0 {
1095 Err(std::io::Error::last_os_error())
1096 } else {
1097 Ok(n as usize)
1098 }
1099}
1100
1101fn request_guest_poweroff() -> AgentdResult<()> {
1102 unsafe {
1103 libc::sync();
1104 }
1105
1106 if crate::handoff::is_pid_1() {
1107 let _ = remount_root_readonly();
1109 unsafe {
1110 libc::sync();
1111 }
1112 let ret = unsafe { libc::reboot(libc::RB_POWER_OFF) };
1113 if ret != 0 {
1114 return Err(std::io::Error::last_os_error().into());
1115 }
1116 return Ok(());
1117 }
1118
1119 if crate::handoff::signal_init_shutdown().is_ok() {
1125 std::thread::sleep(HANDOFF_POWEROFF_TIMEOUT);
1126 }
1127
1128 let _ = crate::handoff::signal_init_term();
1133 Ok(())
1134}
1135
1136fn remount_root_readonly() -> AgentdResult<()> {
1137 let target = std::ffi::CString::new("/").expect("static path contains no NUL");
1138 let ret = unsafe {
1139 libc::mount(
1140 ptr::null(),
1141 target.as_ptr(),
1142 ptr::null(),
1143 (libc::MS_REMOUNT | libc::MS_RDONLY) as libc::c_ulong,
1144 ptr::null(),
1145 )
1146 };
1147
1148 if ret != 0 {
1149 return Err(std::io::Error::last_os_error().into());
1150 }
1151
1152 Ok(())
1153}
1154
1155#[cfg(test)]
1160mod tests {
1161 use super::*;
1162
1163 #[test]
1164 fn record_encoded_guest_messages_counts_only_appended_frames() {
1165 let mut out_buf = Vec::new();
1166 let existing =
1167 Message::with_payload(MessageType::ExecStarted, 1, &ExecStarted { pid: 123 }).unwrap();
1168 codec::encode_to_buf(&existing, &mut out_buf).unwrap();
1169 let start = out_buf.len();
1170
1171 let appended =
1172 Message::with_payload(MessageType::ExecStarted, 2, &ExecStarted { pid: 456 }).unwrap();
1173 codec::encode_to_buf(&appended, &mut out_buf).unwrap();
1174
1175 let mut activity = ActivityTracker::new();
1176 record_encoded_guest_messages(&out_buf, start, &mut activity);
1177
1178 assert_eq!(activity.activity_seq, 1);
1179 assert_eq!(activity.counters.guest_messages, 1);
1180 }
1181
1182 #[test]
1183 fn apply_raw_activity_updates_guest_and_byte_counters() {
1184 let mut activity = ActivityTracker::new();
1185
1186 apply_raw_activity(RawActivity::fs_bytes(42), &mut activity);
1187 apply_raw_activity(RawActivity::tcp_bytes(7), &mut activity);
1188
1189 assert_eq!(activity.activity_seq, 2);
1190 assert_eq!(activity.counters.guest_messages, 2);
1191 assert_eq!(activity.counters.fs_bytes, 42);
1192 assert_eq!(activity.counters.tcp_bytes, 7);
1193 }
1194}