Skip to main content

shutdown_shim/
lib.rs

1// Copyright 2024 The Fuchsia Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4mod 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
42// The amount of time that the shim will spend waiting for a manually trigger
43// system shutdown to finish before forcefully restarting the system.
44const 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    // Check the config
61    let config = Config::from_vmo(&config_vmo.expect("Config VMO handle must be present."))?;
62    println!("[shutdown-shim]: started with config: {:?}", config);
63
64    // Initialize the inspect framework.
65    //
66    // Note that shutdown-shim is a builtin component of ComponentManager, which
67    // means we must setup the inspector in a non-conventional way:
68    // * We must initialize the connection to the inspect sink relative to the
69    // `svc` directory.
70    // * We must not use the global `fuchsia_inspect::component::inspector()`;
71    //   this instance instance is a singleton and would be shared with
72    //   ComponentManager. Instead we declare our own local inspector.
73    let inspector = fuchsia_inspect::Inspector::new(fuchsia_inspect::InspectorConfig::default());
74    let (client, server) =
75        fidl::endpoints::create_endpoints::<fidl_fuchsia_inspect::InspectSinkMarker>();
76    // Note: The inspect server is detached, so we need not poll it.
77    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    /// Tracks the current shutdown request state. Used to ignore shutdown requests while a current
152    /// request is being processed.
153    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                            // Duplicate the VMOs now, as forwarding the mexec request to power-manager
212                            // will consume them.
213                            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    // TODO(https://fxbug.dev/414413282): make `options` non-optional once deprecated shutdown
308    // methods are removed.
309    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        // Return if shutdown is already pending
328        {
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        // We should block on fuchsia.sys.SystemController forever on this task, if
341        // it returns something has gone wrong.
342        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            // We shouldn't still be running at this point
351            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            // Recovery from this state is impossible. Exit with a non-zero exit code,
360            // so our critical marking causes the system to forcefully restart.
361            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    /// Cause the program to terminate.
398    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        // Transform the reasons, and dispatch the request along the standard
417        // reboot pipeline.
418        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}