1mod reboot_reasons;
5mod shutdown_watcher;
6
7use crate::reboot_reasons::ShutdownOptionsWrapper;
8use crate::shutdown_watcher::ShutdownWatcher;
9use anyhow::{Context, format_err};
10use fidl::HandleBased;
11use fidl::endpoints::{DiscoverableProtocolMarker, ServerEnd};
12use fidl_fuchsia_hardware_power_statecontrol::{
13 AdminMexecRequest, AdminRequest, AdminRequestStream, AdminShutdownResponder,
14 RebootMethodsWatcherRegisterRequestStream, RebootReason, ShutdownAction, ShutdownOptions,
15 ShutdownReason, ShutdownWatcherRegisterRequestStream,
16};
17use fidl_fuchsia_power::CollaborativeRebootInitiatorRequestStream;
18use fidl_fuchsia_power_internal::{
19 CollaborativeRebootReason, CollaborativeRebootSchedulerRequestStream,
20};
21use fidl_fuchsia_sys2::SystemControllerMarker;
22use fidl_fuchsia_system_state::{
23 SystemPowerState, SystemStateTransitionRequest, SystemStateTransitionRequestStream,
24};
25use fuchsia_component::client;
26use fuchsia_component::directory::{AsRefDirectory, Directory};
27use fuchsia_component::server::ServiceFs;
28use fuchsia_sync::Mutex;
29use futures::channel::mpsc;
30use futures::lock::Mutex as AMutex;
31use futures::prelude::*;
32use futures::select;
33use shutdown_shim_config::Config;
34use std::pin::pin;
35use std::sync::{Arc, LazyLock};
36use std::time::Duration;
37use {fidl_fuchsia_io as fio, fidl_fuchsia_power_system as fsystem, fuchsia_async as fasync};
38
39mod collaborative_reboot;
40
41const MANUAL_SYSTEM_SHUTDOWN_TIMEOUT: Duration = Duration::from_secs(60 * 60);
44
45enum IncomingRequest {
46 SystemStateTransition(SystemStateTransitionRequestStream),
47 Admin(AdminRequestStream),
48 CollaborativeRebootInitiator(CollaborativeRebootInitiatorRequestStream),
49 CollaborativeRebootScheduler(CollaborativeRebootSchedulerRequestStream),
50 RebootMethodsWatcherRegister(RebootMethodsWatcherRegisterRequestStream),
51 ShutdownWatcherRegister(ShutdownWatcherRegisterRequestStream),
52}
53
54pub async fn main(
55 svc: impl Directory + AsRefDirectory + 'static,
56 directory_request: ServerEnd<fio::DirectoryMarker>,
57 config_vmo: Option<zx::Vmo>,
58) -> Result<(), anyhow::Error> {
59 let config = Config::from_vmo(&config_vmo.expect("Config VMO handle must be present."))?;
61 println!("[shutdown-shim]: started with config: {:?}", config);
62
63 let inspector = fuchsia_inspect::Inspector::new(fuchsia_inspect::InspectorConfig::default());
73 let (client, server) =
74 fidl::endpoints::create_endpoints::<fidl_fuchsia_inspect::InspectSinkMarker>();
75 let _inspect_server_task = inspect_runtime::publish(
77 &inspector,
78 inspect_runtime::PublishOptions::default().on_inspect_sink_client(client),
79 )
80 .ok_or_else(|| format_err!("failed to initialize inspect framework"))?;
81 svc.as_ref_directory()
82 .open(
83 fidl_fuchsia_inspect::InspectSinkMarker::PROTOCOL_NAME,
84 fio::Flags::PROTOCOL_SERVICE,
85 server.into_channel().into(),
86 )
87 .context("failed to connect to InspectSink")?;
88
89 let mut service_fs = ServiceFs::new();
90 service_fs.dir("svc").add_fidl_service(IncomingRequest::Admin);
91 service_fs.dir("svc").add_fidl_service(IncomingRequest::RebootMethodsWatcherRegister);
92 service_fs.dir("svc").add_fidl_service(IncomingRequest::ShutdownWatcherRegister);
93 service_fs.dir("svc").add_fidl_service(IncomingRequest::SystemStateTransition);
94 service_fs.dir("svc").add_fidl_service(IncomingRequest::CollaborativeRebootInitiator);
95 service_fs.dir("svc").add_fidl_service(IncomingRequest::CollaborativeRebootScheduler);
96 service_fs.serve_connection(directory_request).context("failed to serve outgoing namespace")?;
97
98 let (abort_tx, mut abort_rx) = mpsc::unbounded::<()>();
99 let (cr_state, cr_cancellations) = collaborative_reboot::new(&inspector);
100 let ctx = ProgramContext {
101 svc,
102 abort_tx,
103 collaborative_reboot: cr_state,
104 shutdown_pending: Arc::new(AMutex::new(false)),
105 shutdown_watcher: ShutdownWatcher::new_with_inspector(&inspector),
106 config,
107 };
108
109 let shutdown_watcher = ctx.shutdown_watcher.clone();
110 let mut service_fut = service_fs
111 .for_each_concurrent(None, |request: IncomingRequest| async {
112 match request {
113 IncomingRequest::Admin(stream) => ctx.handle_admin_request(stream).await,
114 IncomingRequest::RebootMethodsWatcherRegister(stream) => {
115 shutdown_watcher.clone().handle_reboot_register_request(stream).await;
116 }
117 IncomingRequest::ShutdownWatcherRegister(stream) => {
118 shutdown_watcher.clone().handle_shutdown_register_request(stream).await
119 }
120 IncomingRequest::SystemStateTransition(stream) => {
121 ctx.handle_system_state_transition(stream).await
122 }
123 IncomingRequest::CollaborativeRebootInitiator(stream) => {
124 ctx.collaborative_reboot.handle_initiator_requests(stream, &ctx).await
125 }
126 IncomingRequest::CollaborativeRebootScheduler(stream) => {
127 ctx.collaborative_reboot.handle_scheduler_requests(stream).await
128 }
129 }
130 })
131 .fuse();
132 let collaborative_reboot_cancellation_fut = pin!(cr_cancellations.run());
133 let mut collaborative_reboot_cancellation_fut = collaborative_reboot_cancellation_fut.fuse();
134 let mut abort_fut = abort_rx.next().fuse();
135
136 select! {
137 () = service_fut => {},
138 () = collaborative_reboot_cancellation_fut => unreachable!(),
139 _ = abort_fut => {},
140 };
141
142 Err(format_err!("exited unexpectedly"))
143}
144
145struct ProgramContext<D: Directory + AsRefDirectory> {
146 svc: D,
147 abort_tx: mpsc::UnboundedSender<()>,
148 collaborative_reboot: collaborative_reboot::State,
149
150 shutdown_pending: Arc<AMutex<bool>>,
153
154 shutdown_watcher: Arc<ShutdownWatcher>,
155 config: Config,
156}
157
158impl<D: Directory + AsRefDirectory> ProgramContext<D> {
159 async fn handle_admin_request(&self, mut stream: AdminRequestStream) {
160 while let Ok(Some(request)) = stream.try_next().await {
161 match request {
162 AdminRequest::PowerFullyOn { responder, .. } => {
163 let _ = responder.send(Err(zx::Status::NOT_SUPPORTED.into_raw()));
164 }
165 AdminRequest::Shutdown { options, responder } => {
166 self.shutdown_request(options, responder).await;
167 }
168 AdminRequest::Reboot { reason, responder } => {
171 let _reboot_control_lease = self.acquire_shutdown_control_lease().await;
172 let target_state = if reason == RebootReason::OutOfMemory {
173 SystemPowerState::RebootKernelInitiated
174 } else {
175 SystemPowerState::Reboot
176 };
177 set_system_power_state(target_state);
178 let res = self
179 .forward_command(
180 target_state,
181 Some(ShutdownOptionsWrapper::from_reboot_reason_deprecated(&reason)),
182 None,
183 )
184 .await;
185 let _ = responder.send(res.map_err(|s| s.into_raw()));
186 }
187 AdminRequest::PerformReboot { options, responder } => {
188 let options = match options.reasons {
189 Some(reasons) => {
190 Some(ShutdownOptionsWrapper::from_reboot_reason2_deprecated(&reasons))
191 }
192 None => None,
193 };
194
195 let target_state = if options.as_ref().is_some_and(|options| {
196 options.reasons.contains(&ShutdownReason::OutOfMemory)
197 }) {
198 SystemPowerState::RebootKernelInitiated
199 } else {
200 SystemPowerState::Reboot
201 };
202
203 let res = self.shutdown(target_state, options).await;
204 let _ = responder.send(res.map_err(|s| s.into_raw()));
205 }
206 AdminRequest::RebootToBootloader { responder } => {
207 let res = self.shutdown(SystemPowerState::RebootBootloader, None).await;
208 let _ = responder.send(res.map_err(|s| s.into_raw()));
209 }
210 AdminRequest::RebootToRecovery { responder } => {
211 let res = self.shutdown(SystemPowerState::RebootRecovery, None).await;
212 let _ = responder.send(res.map_err(|s| s.into_raw()));
213 }
214 AdminRequest::Poweroff { responder } => {
215 let res = self.shutdown(SystemPowerState::Poweroff, None).await;
216 let _ = responder.send(res.map_err(|s| s.into_raw()));
217 }
218 AdminRequest::SuspendToRam { responder } => {
219 let target_state = SystemPowerState::SuspendRam;
220 set_system_power_state(target_state);
221 let res = self.forward_command(target_state, None, None).await;
222 let _ = responder.send(res.map_err(|s| s.into_raw()));
223 }
224 AdminRequest::Mexec { responder, kernel_zbi, data_zbi } => {
225 let _reboot_control_lease = self.acquire_shutdown_control_lease().await;
226 let res = async move {
227 let target_state = SystemPowerState::Mexec;
228 {
229 let kernel_zbi =
232 kernel_zbi.duplicate_handle(zx::Rights::SAME_RIGHTS)?;
233 let data_zbi = data_zbi.duplicate_handle(zx::Rights::SAME_RIGHTS)?;
234
235 set_system_power_state(SystemPowerState::Mexec);
236 set_mexec_kernel_zbi(kernel_zbi);
237 set_mexec_data_zbi(data_zbi);
238 }
239
240 self.forward_command(
241 target_state,
242 None,
243 Some(AdminMexecRequest { kernel_zbi, data_zbi }),
244 )
245 .await
246 }
247 .await;
248 let _ = responder.send(res.map_err(|s| s.into_raw()));
249 }
250 }
251 }
252 }
253
254 async fn shutdown_request(&self, options: ShutdownOptions, responder: AdminShutdownResponder) {
255 let Some(action) = options.action else {
256 println!("[shutdown-shim]: error, shutdown action must be specified");
257 let _ = responder.send(Err(zx::Status::INVALID_ARGS.into_raw()));
258 return;
259 };
260
261 let options = match options.reasons {
262 Some(reasons) => ShutdownOptionsWrapper { action, reasons },
263 None => ShutdownOptionsWrapper { action, reasons: vec![] },
264 };
265
266 match action {
267 ShutdownAction::Reboot => {
268 let target_state = if options.reasons.contains(&ShutdownReason::OutOfMemory) {
269 SystemPowerState::RebootKernelInitiated
270 } else {
271 SystemPowerState::Reboot
272 };
273
274 let res = self.shutdown(target_state, Some(options)).await;
275 let _ = responder.send(res.map_err(|s| s.into_raw()));
276 }
277 ShutdownAction::RebootToBootloader => {
278 let res = self.shutdown(SystemPowerState::RebootBootloader, Some(options)).await;
279 let _ = responder.send(res.map_err(|s| s.into_raw()));
280 }
281 ShutdownAction::RebootToRecovery => {
282 let res = self.shutdown(SystemPowerState::RebootRecovery, Some(options)).await;
283 let _ = responder.send(res.map_err(|s| s.into_raw()));
284 }
285 ShutdownAction::Poweroff => {
286 let res = self.shutdown(SystemPowerState::Poweroff, Some(options)).await;
287 let _ = responder.send(res.map_err(|s| s.into_raw()));
288 }
289 ShutdownAction::__SourceBreaking { unknown_ordinal } => {
290 println!(
291 "[shutdown-shim]: error, unrecognized shutdown action ordinal '{unknown_ordinal}'"
292 );
293 let _ = responder.send(Err(zx::Status::INVALID_ARGS.into_raw()));
294 }
295 }
296 }
297
298 async fn handle_system_state_transition(&self, mut stream: SystemStateTransitionRequestStream) {
299 while let Ok(Some(request)) = stream.try_next().await {
300 match request {
301 SystemStateTransitionRequest::GetTerminationSystemState { responder } => {
302 let state = (*SYSTEM_STATE).lock();
303 let _ = responder.send(state.power_state);
304 }
305 SystemStateTransitionRequest::GetMexecZbis { responder } => {
306 let mut state = (*SYSTEM_STATE).lock();
307 if state.power_state != SystemPowerState::Mexec {
308 let _ = responder.send(Err(zx::Status::BAD_STATE.into_raw()));
309 continue;
310 }
311 let kernel_zbi = std::mem::replace(
312 &mut state.mexec_kernel_zbi,
313 zx::NullableHandle::invalid().into(),
314 );
315 let data_zbi = std::mem::replace(
316 &mut state.mexec_data_zbi,
317 zx::NullableHandle::invalid().into(),
318 );
319 let _ = responder.send(Ok((kernel_zbi, data_zbi)));
320 }
321 }
322 }
323 }
324
325 async fn shutdown(
328 &self,
329 target_state: SystemPowerState,
330 options: Option<ShutdownOptionsWrapper>,
331 ) -> Result<(), zx::Status> {
332 println!("[shutdown-shim] shutdown with options [{:?}]", options);
333 let _reboot_control_lease = self.acquire_shutdown_control_lease().await;
334 set_system_power_state(target_state);
335 self.forward_command(target_state, options, None).await
336 }
337
338 async fn forward_command(
339 &self,
340 fallback_state: SystemPowerState,
341 options: Option<ShutdownOptionsWrapper>,
342 _mexec_request: Option<AdminMexecRequest>,
343 ) -> Result<(), zx::Status> {
344 println!("[shutdown-shim] entering {:?} state", fallback_state);
345 {
347 let mut shutdown_pending = self.shutdown_pending.lock().await;
348 if *shutdown_pending {
349 return Err(zx::Status::ALREADY_EXISTS);
350 }
351 *shutdown_pending = true;
352 }
353
354 if let Some(options) = options
355 && !options.reasons.is_empty()
356 {
357 self.shutdown_watcher.handle_system_shutdown_message(options).await;
358 }
359
360 self.drive_shutdown_manually().await;
361
362 eprintln!("[shutdown-shim]: we shouldn't still be running, crashing the system");
365 Self::abort(self.abort_tx.clone()).await
366 }
367
368 async fn drive_shutdown_manually(&self) {
369 let abort_tx = self.abort_tx.clone();
370 fasync::Task::spawn(async {
371 fasync::Timer::new(MANUAL_SYSTEM_SHUTDOWN_TIMEOUT).await;
372 Self::abort(abort_tx).await;
374 })
375 .detach();
376
377 if let Err(e) = self.initiate_component_shutdown().await {
378 eprintln!(
379 "[shutdown-shim]: error initiating component shutdown, system shutdown impossible: {e}"
380 );
381 Self::abort(self.abort_tx.clone()).await;
384 }
385 }
386
387 async fn initiate_component_shutdown(&self) -> Result<(), anyhow::Error> {
388 println!("[shutdown-shim] shutting down components");
389 let system_controller_client = self
390 .connect_to_protocol::<SystemControllerMarker>()
391 .context("error connecting to component_manager")?;
392
393 system_controller_client.shutdown().await.context("failed to initiate shutdown")
394 }
395
396 async fn acquire_shutdown_control_lease(&self) -> Option<zx::EventPair> {
397 if !self.config.suspend_enabled {
398 return None;
399 }
400 let res = async {
401 let activity_governor = self
402 .connect_to_protocol::<fsystem::ActivityGovernorMarker>()
403 .context("error connecting to system_activity_governor")?;
404 activity_governor
405 .take_wake_lease("shutdown_control")
406 .await
407 .context("failed to take wake lease")
408 }
409 .await;
410 res.map_err(|e| {
411 eprintln!("[shutdown-shim]: {e}");
412 ()
413 })
414 .ok()
415 }
416
417 async fn abort(mut abort_tx: mpsc::UnboundedSender<()>) -> ! {
419 let _ = abort_tx.send(()).await;
420 std::future::pending::<()>().await;
421 unreachable!();
422 }
423
424 fn connect_to_protocol<P: DiscoverableProtocolMarker>(
425 &self,
426 ) -> Result<P::Proxy, anyhow::Error> {
427 client::connect_to_protocol_at_dir_root::<P>(&self.svc)
428 }
429}
430
431impl<D: Directory + AsRefDirectory> collaborative_reboot::RebootActuator for ProgramContext<D> {
432 async fn perform_reboot(
433 &self,
434 reasons: Vec<CollaborativeRebootReason>,
435 ) -> Result<(), zx::Status> {
436 let reasons = reasons
439 .into_iter()
440 .map(|reason| match reason {
441 CollaborativeRebootReason::NetstackMigration => ShutdownReason::NetstackMigration,
442 CollaborativeRebootReason::SystemUpdate => ShutdownReason::SystemUpdate,
443 })
444 .collect();
445 self.shutdown(
446 SystemPowerState::Reboot,
447 Some(ShutdownOptionsWrapper { action: ShutdownAction::Reboot, reasons }),
448 )
449 .await
450 }
451}
452
453struct SystemState {
454 power_state: SystemPowerState,
455 mexec_kernel_zbi: zx::Vmo,
456 mexec_data_zbi: zx::Vmo,
457}
458
459impl SystemState {
460 fn new() -> Self {
461 Self {
462 power_state: SystemPowerState::FullyOn,
463 mexec_kernel_zbi: zx::NullableHandle::invalid().into(),
464 mexec_data_zbi: zx::NullableHandle::invalid().into(),
465 }
466 }
467}
468
469static SYSTEM_STATE: LazyLock<Mutex<SystemState>> =
470 LazyLock::new(|| Mutex::new(SystemState::new()));
471
472fn set_system_power_state(new: SystemPowerState) {
473 let mut s = (*SYSTEM_STATE).lock();
474 s.power_state = new;
475}
476
477fn set_mexec_kernel_zbi(new: zx::Vmo) {
478 let mut s = (*SYSTEM_STATE).lock();
479 s.mexec_kernel_zbi = new;
480}
481
482fn set_mexec_data_zbi(new: zx::Vmo) {
483 let mut s = (*SYSTEM_STATE).lock();
484 s.mexec_data_zbi = new;
485}