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