1use crate::platform::PlatformServices;
6use anyhow::{Error, anyhow};
7use fidl::endpoints::{Proxy, create_proxy};
8use fidl_fuchsia_virtualization::{GuestManagerProxy, GuestMarker, GuestProxy, GuestStatus};
9use fuchsia_async::{self as fasync, TimeoutExt};
10use guest_cli_args as arguments;
11use std::fmt;
12use zx_status::Status;
13
14#[derive(Default, serde::Serialize, serde::Deserialize, PartialEq, Debug)]
15pub enum StopStatus {
16 #[default]
17 NotStopped,
18 NotRunning,
19 Forced,
20 Graceful,
21}
22
23#[derive(Default, serde::Serialize, serde::Deserialize)]
24pub struct StopResult {
25 pub status: StopStatus,
26 pub stop_time_nanos: i64,
27}
28
29impl fmt::Display for StopResult {
30 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
31 let time_to_str = |nanos: i64| -> String {
32 let duration = std::time::Duration::from_nanos(nanos as u64);
33 if duration.as_millis() > 1 {
34 format!("{}ms", duration.as_millis())
35 } else {
36 format!("{}μs", duration.as_micros())
37 }
38 };
39
40 match self.status {
41 StopStatus::NotStopped => write!(f, "Failed to stop guest"),
42 StopStatus::NotRunning => write!(f, "Nothing to do - the guest is not running"),
43 StopStatus::Forced => {
44 write!(f, "Guest forced to stop in {}", time_to_str(self.stop_time_nanos))
45 }
46 StopStatus::Graceful => {
47 write!(f, "Guest finished stopping in {}", time_to_str(self.stop_time_nanos))
48 }
49 }
50 }
51}
52
53enum ShutdownCommand {
54 DebianShutdownCommand,
55 ZirconShutdownCommand,
56}
57
58pub async fn handle_stop<P: PlatformServices>(
59 services: &P,
60 args: &arguments::stop_args::StopArgs,
61) -> Result<StopResult, Error> {
62 let manager = services.connect_to_manager(args.guest_type).await?;
63 let status = manager.get_info().await?.guest_status.expect("guest status should always be set");
64 if status != GuestStatus::Starting && status != GuestStatus::Running {
65 return Ok(StopResult { status: StopStatus::NotRunning, ..StopResult::default() });
66 }
67
68 if args.force {
69 force_stop_guest(args.guest_type, manager).await
70 } else {
71 graceful_stop_guest(args.guest_type, manager).await
72 }
73}
74
75fn get_graceful_stop_command(guest_cmd: ShutdownCommand) -> Vec<u8> {
76 let arg_string = match guest_cmd {
77 ShutdownCommand::ZirconShutdownCommand => "power shutdown\n".to_string(),
78 ShutdownCommand::DebianShutdownCommand => "shutdown now\n".to_string(),
79 };
80
81 arg_string.into_bytes()
82}
83
84async fn send_stop_shell_command(
85 guest_cmd: ShutdownCommand,
86 guest_endpoint: GuestProxy,
87) -> Result<(), Error> {
88 let socket = guest_endpoint
90 .get_console()
91 .await
92 .map_err(|err| anyhow!("failed to get a get_console response: {}", err))?
93 .map_err(|err| anyhow!("get_console failed with: {:?}", err))?;
94
95 println!("Sending stop command to guest");
96 let command = get_graceful_stop_command(guest_cmd);
97 let bytes_written = socket
98 .write(&command)
99 .map_err(|err| anyhow!("failed to write command to socket: {}", err))?;
100 if bytes_written != command.len() {
101 return Err(anyhow!(
102 "attempted to send command '{}', but only managed to write '{}'",
103 std::str::from_utf8(&command).expect("failed to parse as utf-8"),
104 std::str::from_utf8(&command[0..bytes_written]).expect("failed to parse as utf-8")
105 ));
106 }
107
108 Ok(())
109}
110
111async fn graceful_stop_guest(
112 guest: arguments::GuestType,
113 manager: GuestManagerProxy,
114) -> Result<StopResult, Error> {
115 let (guest_endpoint, guest_server_end) = create_proxy::<GuestMarker>();
116 manager
117 .connect(guest_server_end)
118 .await
119 .map_err(|err| anyhow!("failed to get a connect response: {}", err))?
120 .map_err(|err| anyhow!("connect failed with: {:?}", err))?;
121
122 match guest {
123 arguments::GuestType::Zircon => {
124 send_stop_shell_command(ShutdownCommand::ZirconShutdownCommand, guest_endpoint.clone())
125 .await
126 }
127 arguments::GuestType::Debian => {
128 send_stop_shell_command(ShutdownCommand::DebianShutdownCommand, guest_endpoint.clone())
129 .await
130 }
131 }?;
132
133 let start = fasync::MonotonicInstant::now();
134 println!("Waiting for guest to stop");
135
136 let unresponsive_help_delay =
137 fasync::MonotonicInstant::now() + std::time::Duration::from_secs(10).into();
138 let guest_closed =
139 guest_endpoint.on_closed().on_timeout(unresponsive_help_delay, || Err(Status::TIMED_OUT));
140
141 match guest_closed.await {
142 Ok(_) => Ok(()),
143 Err(Status::TIMED_OUT) => {
144 println!("If the guest is unresponsive, you may force stop it by passing -f");
145 guest_endpoint.on_closed().await.map(|_| ())
146 }
147 Err(err) => Err(err),
148 }
149 .map_err(|err| anyhow!("failed to wait on guest stop signal: {}", err))?;
150
151 let stop_time_nanos = get_time_nanos(fasync::MonotonicInstant::now() - start);
152 Ok(StopResult { status: StopStatus::Graceful, stop_time_nanos })
153}
154
155async fn force_stop_guest(
156 guest: arguments::GuestType,
157 manager: GuestManagerProxy,
158) -> Result<StopResult, Error> {
159 println!("Forcing {} to stop", guest);
160 let start = fasync::MonotonicInstant::now();
161 manager.force_shutdown().await?;
162
163 let stop_time_nanos = get_time_nanos(fasync::MonotonicInstant::now() - start);
164 Ok(StopResult { status: StopStatus::Forced, stop_time_nanos })
165}
166
167fn get_time_nanos(duration: fasync::MonotonicDuration) -> i64 {
168 #[cfg(target_os = "fuchsia")]
169 let nanos = duration.into_nanos();
170
171 #[cfg(not(target_os = "fuchsia"))]
172 let nanos = duration.as_nanos().try_into().unwrap();
173
174 nanos
175}
176
177#[cfg(test)]
178mod test {
179 use super::*;
180 use async_utils::PollExt;
181 use fidl::Socket;
182 use fidl::endpoints::create_proxy_and_stream;
183 use fidl_fuchsia_virtualization::GuestManagerMarker;
184 use futures::TryStreamExt;
185
186 #[test]
187 fn graceful_stop_waits_for_shutdown() {
188 let mut executor = fasync::TestExecutor::new_with_fake_time();
189 executor.set_fake_time(fuchsia_async::MonotonicInstant::now());
190
191 let (manager_proxy, mut manager_stream) = create_proxy_and_stream::<GuestManagerMarker>();
192
193 let fut = graceful_stop_guest(arguments::GuestType::Debian, manager_proxy);
194 futures::pin_mut!(fut);
195
196 assert!(executor.run_until_stalled(&mut fut).is_pending());
197
198 let (guest_server_end, responder) = executor
199 .run_until_stalled(&mut manager_stream.try_next())
200 .expect("future should be ready")
201 .unwrap()
202 .unwrap()
203 .into_connect()
204 .expect("received unexpected request on stream");
205
206 responder.send(Ok(())).expect("failed to send response");
207 let mut guest_stream = guest_server_end.into_stream();
208
209 assert!(executor.run_until_stalled(&mut fut).is_pending());
210
211 let responder = executor
212 .run_until_stalled(&mut guest_stream.try_next())
213 .expect("future should be ready")
214 .unwrap()
215 .unwrap()
216 .into_get_console()
217 .expect("received unexpected request on stream");
218
219 let (client, device) = Socket::create_stream();
220 responder.send(Ok(client)).expect("failed to send response");
221
222 assert!(executor.run_until_stalled(&mut fut).is_pending());
223
224 let expected_command = get_graceful_stop_command(ShutdownCommand::DebianShutdownCommand);
225 let mut actual_command = vec![0u8; expected_command.len()];
226 assert_eq!(device.read(actual_command.as_mut_slice()).unwrap(), expected_command.len());
227
228 let duration = std::time::Duration::from_secs(10) + std::time::Duration::from_nanos(1);
230 executor.set_fake_time(fasync::MonotonicInstant::after((duration).into()));
231
232 assert!(executor.run_until_stalled(&mut fut).is_pending());
235
236 drop(guest_stream);
238
239 let result = executor.run_until_stalled(&mut fut).expect("future should be ready").unwrap();
240 assert_eq!(result.status, StopStatus::Graceful);
241 assert_eq!(result.stop_time_nanos, duration.as_nanos() as i64);
242 }
243
244 #[test]
245 fn force_stop_guest_calls_stop_endpoint() {
246 let mut executor = fasync::TestExecutor::new();
247 let (proxy, mut stream) = create_proxy_and_stream::<GuestManagerMarker>();
248
249 let fut = force_stop_guest(arguments::GuestType::Debian, proxy);
250 futures::pin_mut!(fut);
251
252 assert!(executor.run_until_stalled(&mut fut).is_pending());
253
254 let responder = executor
255 .run_until_stalled(&mut stream.try_next())
256 .expect("future should be ready")
257 .unwrap()
258 .unwrap()
259 .into_force_shutdown()
260 .expect("received unexpected request on stream");
261 responder.send().expect("failed to send response");
262
263 let result = executor.run_until_stalled(&mut fut).expect("future should be ready").unwrap();
264 assert_eq!(result.status, StopStatus::Forced);
265 }
266}