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, ShutdownAction, ShutdownOptions, ShutdownReason,
15 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::PerformReboot { options, responder } => {
169 let options = match options.reasons {
170 Some(reasons) => {
171 Some(ShutdownOptionsWrapper::from_reboot_reason2_deprecated(&reasons))
172 }
173 None => None,
174 };
175
176 let target_state = if options.as_ref().is_some_and(|options| {
177 options.reasons.contains(&ShutdownReason::OutOfMemory)
178 }) {
179 SystemPowerState::RebootKernelInitiated
180 } else {
181 SystemPowerState::Reboot
182 };
183
184 let res = self.shutdown(target_state, options).await;
185 let _ = responder.send(res.map_err(|s| s.into_raw()));
186 }
187 AdminRequest::RebootToBootloader { responder } => {
188 let res = self.shutdown(SystemPowerState::RebootBootloader, None).await;
189 let _ = responder.send(res.map_err(|s| s.into_raw()));
190 }
191 AdminRequest::RebootToRecovery { responder } => {
192 let res = self.shutdown(SystemPowerState::RebootRecovery, None).await;
193 let _ = responder.send(res.map_err(|s| s.into_raw()));
194 }
195 AdminRequest::Poweroff { responder } => {
196 let res = self.shutdown(SystemPowerState::Poweroff, None).await;
197 let _ = responder.send(res.map_err(|s| s.into_raw()));
198 }
199 AdminRequest::SuspendToRam { responder } => {
200 let target_state = SystemPowerState::SuspendRam;
201 set_system_power_state(target_state);
202 let res = self.forward_command(target_state, None, None).await;
203 let _ = responder.send(res.map_err(|s| s.into_raw()));
204 }
205 AdminRequest::Mexec { responder, kernel_zbi, data_zbi } => {
206 let _reboot_control_lease = self.acquire_shutdown_control_lease().await;
207 let res = async move {
208 let target_state = SystemPowerState::Mexec;
209 {
210 let kernel_zbi =
213 kernel_zbi.duplicate_handle(zx::Rights::SAME_RIGHTS)?;
214 let data_zbi = data_zbi.duplicate_handle(zx::Rights::SAME_RIGHTS)?;
215
216 set_system_power_state(SystemPowerState::Mexec);
217 set_mexec_kernel_zbi(kernel_zbi);
218 set_mexec_data_zbi(data_zbi);
219 }
220
221 self.forward_command(
222 target_state,
223 None,
224 Some(AdminMexecRequest { kernel_zbi, data_zbi }),
225 )
226 .await
227 }
228 .await;
229 let _ = responder.send(res.map_err(|s| s.into_raw()));
230 }
231 }
232 }
233 }
234
235 async fn shutdown_request(&self, options: ShutdownOptions, responder: AdminShutdownResponder) {
236 let Some(action) = options.action else {
237 println!("[shutdown-shim]: error, shutdown action must be specified");
238 let _ = responder.send(Err(zx::Status::INVALID_ARGS.into_raw()));
239 return;
240 };
241
242 let options = match options.reasons {
243 Some(reasons) => ShutdownOptionsWrapper { action, reasons },
244 None => ShutdownOptionsWrapper { action, reasons: vec![] },
245 };
246
247 match action {
248 ShutdownAction::Reboot => {
249 let target_state = if options.reasons.contains(&ShutdownReason::OutOfMemory) {
250 SystemPowerState::RebootKernelInitiated
251 } else {
252 SystemPowerState::Reboot
253 };
254
255 let res = self.shutdown(target_state, Some(options)).await;
256 let _ = responder.send(res.map_err(|s| s.into_raw()));
257 }
258 ShutdownAction::RebootToBootloader => {
259 let res = self.shutdown(SystemPowerState::RebootBootloader, Some(options)).await;
260 let _ = responder.send(res.map_err(|s| s.into_raw()));
261 }
262 ShutdownAction::RebootToRecovery => {
263 let res = self.shutdown(SystemPowerState::RebootRecovery, Some(options)).await;
264 let _ = responder.send(res.map_err(|s| s.into_raw()));
265 }
266 ShutdownAction::Poweroff => {
267 let res = self.shutdown(SystemPowerState::Poweroff, Some(options)).await;
268 let _ = responder.send(res.map_err(|s| s.into_raw()));
269 }
270 ShutdownAction::__SourceBreaking { unknown_ordinal } => {
271 println!(
272 "[shutdown-shim]: error, unrecognized shutdown action ordinal '{unknown_ordinal}'"
273 );
274 let _ = responder.send(Err(zx::Status::INVALID_ARGS.into_raw()));
275 }
276 }
277 }
278
279 async fn handle_system_state_transition(&self, mut stream: SystemStateTransitionRequestStream) {
280 while let Ok(Some(request)) = stream.try_next().await {
281 match request {
282 SystemStateTransitionRequest::GetTerminationSystemState { responder } => {
283 let state = (*SYSTEM_STATE).lock();
284 let _ = responder.send(state.power_state);
285 }
286 SystemStateTransitionRequest::GetMexecZbis { responder } => {
287 let mut state = (*SYSTEM_STATE).lock();
288 if state.power_state != SystemPowerState::Mexec {
289 let _ = responder.send(Err(zx::Status::BAD_STATE.into_raw()));
290 continue;
291 }
292 let kernel_zbi = std::mem::replace(
293 &mut state.mexec_kernel_zbi,
294 zx::NullableHandle::invalid().into(),
295 );
296 let data_zbi = std::mem::replace(
297 &mut state.mexec_data_zbi,
298 zx::NullableHandle::invalid().into(),
299 );
300 let _ = responder.send(Ok((kernel_zbi, data_zbi)));
301 }
302 }
303 }
304 }
305
306 async fn shutdown(
309 &self,
310 target_state: SystemPowerState,
311 options: Option<ShutdownOptionsWrapper>,
312 ) -> Result<(), zx::Status> {
313 println!("[shutdown-shim] shutdown with options [{:?}]", options);
314 let _reboot_control_lease = self.acquire_shutdown_control_lease().await;
315 set_system_power_state(target_state);
316 self.forward_command(target_state, options, None).await
317 }
318
319 async fn forward_command(
320 &self,
321 fallback_state: SystemPowerState,
322 options: Option<ShutdownOptionsWrapper>,
323 _mexec_request: Option<AdminMexecRequest>,
324 ) -> Result<(), zx::Status> {
325 println!("[shutdown-shim] entering {:?} state", fallback_state);
326 {
328 let mut shutdown_pending = self.shutdown_pending.lock().await;
329 if *shutdown_pending {
330 return Err(zx::Status::ALREADY_EXISTS);
331 }
332 *shutdown_pending = true;
333 }
334
335 if let Some(options) = options {
336 self.shutdown_watcher.handle_system_shutdown_message(options).await;
337 }
338
339 self.drive_shutdown_manually().await;
340
341 eprintln!("[shutdown-shim]: we shouldn't still be running, crashing the system");
344 Self::abort(self.abort_tx.clone()).await
345 }
346
347 async fn drive_shutdown_manually(&self) {
348 let abort_tx = self.abort_tx.clone();
349 fasync::Task::spawn(async {
350 fasync::Timer::new(MANUAL_SYSTEM_SHUTDOWN_TIMEOUT).await;
351 Self::abort(abort_tx).await;
353 })
354 .detach();
355
356 if let Err(e) = self.initiate_component_shutdown().await {
357 eprintln!(
358 "[shutdown-shim]: error initiating component shutdown, system shutdown impossible: {e}"
359 );
360 Self::abort(self.abort_tx.clone()).await;
363 }
364 }
365
366 async fn initiate_component_shutdown(&self) -> Result<(), anyhow::Error> {
367 println!("[shutdown-shim] shutting down components");
368 let system_controller_client = self
369 .connect_to_protocol::<SystemControllerMarker>()
370 .context("error connecting to component_manager")?;
371
372 system_controller_client.shutdown().await.context("failed to initiate shutdown")
373 }
374
375 async fn acquire_shutdown_control_lease(&self) -> Option<zx::EventPair> {
376 if !self.config.suspend_enabled {
377 return None;
378 }
379 let res = async {
380 let activity_governor = self
381 .connect_to_protocol::<fsystem::ActivityGovernorMarker>()
382 .context("error connecting to system_activity_governor")?;
383 activity_governor
384 .take_wake_lease("shutdown_control")
385 .await
386 .context("failed to take wake lease")
387 }
388 .await;
389 res.map_err(|e| {
390 eprintln!("[shutdown-shim]: {e}");
391 ()
392 })
393 .ok()
394 }
395
396 async fn abort(mut abort_tx: mpsc::UnboundedSender<()>) -> ! {
398 let _ = abort_tx.send(()).await;
399 std::future::pending::<()>().await;
400 unreachable!();
401 }
402
403 fn connect_to_protocol<P: DiscoverableProtocolMarker>(
404 &self,
405 ) -> Result<P::Proxy, anyhow::Error> {
406 client::connect_to_protocol_at_dir_root::<P>(&self.svc)
407 }
408}
409
410impl<D: Directory + AsRefDirectory> collaborative_reboot::RebootActuator for ProgramContext<D> {
411 async fn perform_reboot(
412 &self,
413 reasons: Vec<CollaborativeRebootReason>,
414 ) -> Result<(), zx::Status> {
415 let reasons = reasons
418 .into_iter()
419 .map(|reason| match reason {
420 CollaborativeRebootReason::NetstackMigration => ShutdownReason::NetstackMigration,
421 CollaborativeRebootReason::SystemUpdate => ShutdownReason::SystemUpdate,
422 })
423 .collect();
424 self.shutdown(
425 SystemPowerState::Reboot,
426 Some(ShutdownOptionsWrapper { action: ShutdownAction::Reboot, reasons }),
427 )
428 .await
429 }
430}
431
432struct SystemState {
433 power_state: SystemPowerState,
434 mexec_kernel_zbi: zx::Vmo,
435 mexec_data_zbi: zx::Vmo,
436}
437
438impl SystemState {
439 fn new() -> Self {
440 Self {
441 power_state: SystemPowerState::FullyOn,
442 mexec_kernel_zbi: zx::NullableHandle::invalid().into(),
443 mexec_data_zbi: zx::NullableHandle::invalid().into(),
444 }
445 }
446}
447
448static SYSTEM_STATE: LazyLock<Mutex<SystemState>> =
449 LazyLock::new(|| Mutex::new(SystemState::new()));
450
451fn set_system_power_state(new: SystemPowerState) {
452 let mut s = (*SYSTEM_STATE).lock();
453 s.power_state = new;
454}
455
456fn set_mexec_kernel_zbi(new: zx::Vmo) {
457 let mut s = (*SYSTEM_STATE).lock();
458 s.mexec_kernel_zbi = new;
459}
460
461fn set_mexec_data_zbi(new: zx::Vmo) {
462 let mut s = (*SYSTEM_STATE).lock();
463 s.mexec_data_zbi = new;
464}