1use crate::node_remover::NodeRemover;
6use async_trait::async_trait;
7use fidl_fuchsia_diagnostics as fdiagnostics;
8use fidl_fuchsia_kernel as fkernel;
9use fidl_fuchsia_process_lifecycle as flifecycle;
10use fidl_fuchsia_system_state as fsystem_state;
11use fuchsia_async as fasync;
12use fuchsia_component::client::{connect_to_protocol, connect_to_protocol_sync};
13use fuchsia_component::server::{FidlService, ServiceFs, ServiceObjLocal};
14use futures::channel::oneshot;
15use futures::prelude::*;
16use log::{error, info, warn};
17use std::cell::RefCell;
18use std::rc::Rc;
19use zx::sys::{
20 ZX_SYSTEM_POWERCTL_ACK_KERNEL_INITIATED_REBOOT, ZX_SYSTEM_POWERCTL_REBOOT,
21 ZX_SYSTEM_POWERCTL_REBOOT_BOOTLOADER, ZX_SYSTEM_POWERCTL_REBOOT_RECOVERY,
22 ZX_SYSTEM_POWERCTL_SHUTDOWN,
23};
24
25#[async_trait(?Send)]
27pub trait SystemPower {
28 async fn get_system_power_state(&self) -> fsystem_state::SystemPowerState;
30
31 fn system_powerctl(&self, cmd: u32) -> Result<(), zx::Status>;
33
34 fn has_resources(&self) -> bool;
36
37 fn exit(&self, status: i32);
39}
40
41pub struct RealSystemPower {
43 power_resource: Option<zx::Resource>,
44}
45
46impl RealSystemPower {
47 pub fn new(power_resource: Option<zx::Resource>) -> Self {
48 Self { power_resource }
49 }
50}
51
52#[async_trait(?Send)]
53impl SystemPower for RealSystemPower {
54 async fn get_system_power_state(&self) -> fsystem_state::SystemPowerState {
55 get_system_power_state().await
56 }
57
58 fn system_powerctl(&self, cmd: u32) -> Result<(), zx::Status> {
59 let Some(power_resource) = &self.power_resource else {
60 return Err(zx::Status::INVALID_ARGS);
61 };
62 zx::Status::ok(unsafe { zx::sys::zx_system_powerctl(power_resource.raw_handle(), cmd) })
63 }
64
65 fn has_resources(&self) -> bool {
66 self.power_resource.is_some()
67 }
68
69 fn exit(&self, status: i32) {
70 std::process::exit(status);
71 }
72}
73
74#[async_trait(?Send)]
75impl<T: SystemPower + ?Sized> SystemPower for Rc<T> {
76 async fn get_system_power_state(&self) -> fsystem_state::SystemPowerState {
77 (**self).get_system_power_state().await
78 }
79 fn system_powerctl(&self, cmd: u32) -> Result<(), zx::Status> {
80 (**self).system_powerctl(cmd)
81 }
82 fn has_resources(&self) -> bool {
83 (**self).has_resources()
84 }
85 fn exit(&self, status: i32) {
86 (**self).exit(status)
87 }
88}
89
90#[derive(Copy, Clone, PartialEq, Debug)]
91enum State {
92 Running,
93 PackageStopping,
94 PackageStopped,
95 BootStopping,
96 Stopped,
97}
98
99type ShutdownSender = oneshot::Sender<Result<(), zx::Status>>;
100
101struct LifecycleServer {
102 on_stop: RefCell<Option<oneshot::Sender<ShutdownSender>>>,
103}
104
105impl LifecycleServer {
106 fn new(on_stop: oneshot::Sender<ShutdownSender>) -> Self {
107 Self { on_stop: RefCell::new(Some(on_stop)) }
108 }
109
110 async fn serve(
111 self: Rc<Self>,
112 mut stream: flifecycle::LifecycleRequestStream,
113 ) -> Result<(), fidl::Error> {
114 if let Some(request) = stream.try_next().await? {
115 match request {
116 flifecycle::LifecycleRequest::Stop { control_handle } => {
117 let (tx, rx) = oneshot::channel();
118 let on_stop = self.on_stop.borrow_mut().take();
119 if let Some(on_stop) = on_stop {
120 let _ = on_stop.send(tx);
121 if let Ok(result) = rx.await {
122 control_handle.shutdown_with_epitaph(result);
123 } else {
124 control_handle.shutdown_with_epitaph(zx::Status::INTERNAL);
125 }
126 }
127 }
128 }
129 }
130 Ok(())
131 }
132}
133
134struct ShutdownManagerState {
135 state: State,
136 received_boot_shutdown_signal: bool,
137 package_shutdown_complete_callbacks: Vec<ShutdownSender>,
138 boot_shutdown_complete_callbacks: Vec<ShutdownSender>,
139 lifecycle_stop: bool,
140}
141
142pub struct ShutdownManager<P: SystemPower + 'static = RealSystemPower> {
143 node_remover: Rc<dyn NodeRemover>,
144 log_flush: Option<fdiagnostics::LogFlusherProxy>,
145 internal_state: RefCell<ShutdownManagerState>,
146 scope: fasync::Scope,
147 system_power: P,
148}
149
150fn get_power_resource() -> Result<zx::Resource, anyhow::Error> {
151 let client = connect_to_protocol_sync::<fkernel::PowerResourceMarker>()?;
152 let resource = client.get(zx::MonotonicInstant::INFINITE)?;
153 Ok(resource)
154}
155
156async fn get_system_power_state() -> fsystem_state::SystemPowerState {
157 let client = match connect_to_protocol::<fsystem_state::SystemStateTransitionMarker>() {
158 Ok(c) => c,
159 Err(e) => {
160 error!("Failed to connect to StateStateTransition: {}, falling back to default", e);
161 return fsystem_state::SystemPowerState::Reboot;
162 }
163 };
164
165 match client.get_termination_system_state().await {
166 Ok(state) => state,
167 Err(e) => {
168 error!("Failed to get termination system state: {}, falling back to default", e);
169 fsystem_state::SystemPowerState::Reboot
170 }
171 }
172}
173
174impl ShutdownManager<RealSystemPower> {
175 pub fn from_incoming(node_remover: Rc<dyn NodeRemover>) -> Rc<Self> {
177 let power_resource = get_power_resource()
178 .inspect_err(|e| {
179 info!("Failed to get power resource, assuming test environment: {}", e)
180 })
181 .ok();
182 let log_flush = connect_to_protocol::<fdiagnostics::LogFlusherMarker>()
183 .inspect_err(|e| error!("Failed to connect to LogFlusher: {}", e))
184 .ok();
185 Self::new(node_remover, RealSystemPower::new(power_resource), log_flush)
186 }
187}
188
189impl<P: SystemPower + 'static> ShutdownManager<P> {
190 pub fn new(
191 node_remover: Rc<dyn NodeRemover>,
192 system_power: P,
193 log_flush: Option<fdiagnostics::LogFlusherProxy>,
194 ) -> Rc<Self> {
195 let shutdown_manager = Rc::new(Self {
196 node_remover: node_remover.clone(),
197 internal_state: RefCell::new(ShutdownManagerState {
198 state: State::Running,
199 received_boot_shutdown_signal: false,
200 package_shutdown_complete_callbacks: Vec::new(),
201 boot_shutdown_complete_callbacks: Vec::new(),
202 lifecycle_stop: false,
203 }),
204 log_flush,
205 scope: fasync::Scope::new_with_name("shutdown_manager"),
206 system_power,
207 });
208
209 let weak_manager = Rc::downgrade(&shutdown_manager);
210 node_remover.set_on_removal_timeout_callback(Box::new(move || {
211 if let Some(strong_manager) = weak_manager.upgrade() {
212 info!(
213 "Timed out waiting for nodes to be removed, issuing syscall to reboot/shutdown"
214 );
215 let strong_manager_clone = strong_manager.clone();
216 strong_manager.scope.spawn_local(async move {
217 strong_manager_clone.execute_shutdown_strategy().await;
218 });
219 }
220 }));
221
222 shutdown_manager
223 }
224
225 pub fn publish<'a>(self: &Rc<Self>, fs: &mut ServiceFs<ServiceObjLocal<'a, ()>>) {
226 let self_clone = self.clone();
227 let (tx, rx) = oneshot::channel::<ShutdownSender>();
228 self.scope.spawn_local(async move {
229 if let Ok(sender) = rx.await {
230 let status = self_clone.signal_package_shutdown().await;
231 let _ = sender.send(status);
232 }
233 });
234 let devfs_with_pkg_lifecycle = Rc::new(LifecycleServer::new(tx));
235
236 let scope = self.scope.as_handle().clone();
237 fs.dir("svc").add_service_at(
238 "fuchsia.device.fs.with.pkg.lifecycle.Lifecycle",
239 FidlService::from(move |stream: flifecycle::LifecycleRequestStream| {
240 let devfs_with_pkg_lifecycle = devfs_with_pkg_lifecycle.clone();
241 scope.spawn_local(async move {
242 devfs_with_pkg_lifecycle.serve(stream).await.unwrap_or_else(|e| {
243 error!("Failed to serve devfs with pkg lifecycle: {}", e)
244 });
245 });
246 }),
247 );
248
249 let self_clone = self.clone();
250 let (tx, rx) = oneshot::channel::<ShutdownSender>();
251 self.scope.spawn_local(async move {
252 if let Ok(sender) = rx.await {
253 let status = self_clone.signal_boot_shutdown().await;
254 let _ = sender.send(status);
255 }
256 });
257 let devfs_lifecycle = Rc::new(LifecycleServer::new(tx));
258
259 let scope = self.scope.as_handle().clone();
260 fs.dir("svc").add_service_at(
261 "fuchsia.device.fs.lifecycle.Lifecycle",
262 FidlService::from(move |stream: flifecycle::LifecycleRequestStream| {
263 let devfs_lifecycle = devfs_lifecycle.clone();
264 scope.spawn_local(async move {
265 devfs_lifecycle
266 .serve(stream)
267 .await
268 .unwrap_or_else(|e| error!("Failed to serve devfs lifecycle: {}", e));
269 });
270 }),
271 );
272
273 let self_clone = self.clone();
275 let (tx, rx) = oneshot::channel::<ShutdownSender>();
276 self.scope.spawn_local(async move {
277 if let Ok(sender) = rx.await {
278 self_clone.internal_state.borrow_mut().lifecycle_stop = true;
279 let status = self_clone.signal_boot_shutdown().await;
280 let _ = sender.send(status);
281 }
282 });
283 let lifecycle_server = Rc::new(LifecycleServer::new(tx));
284
285 if let Some(handle) =
286 fuchsia_runtime::take_startup_handle(fuchsia_runtime::HandleType::Lifecycle.into())
287 {
288 let channel = zx::Channel::from(handle);
289 let server_end =
290 fidl::endpoints::ServerEnd::<flifecycle::LifecycleMarker>::new(channel);
291 let stream = server_end.into_stream();
292
293 let self_clone = self.clone();
294 self.scope.spawn_local(async move {
295 if let Err(e) = lifecycle_server.serve(stream).await {
296 error!("Lifecycle connection got unbound: {}", e);
297 let _ = self_clone.signal_boot_shutdown().await;
299 }
300 });
301 } else {
302 info!(concat!(
303 "No valid handle found for lifecycle events, assuming test environment ",
304 "and continuing"
305 ));
306 }
307 }
308
309 async fn on_package_shutdown_complete(&self) {
310 info!("Package shutdown complete");
311 let received_boot_shutdown_signal = {
312 let mut internal_state = self.internal_state.borrow_mut();
313 assert_eq!(internal_state.state, State::PackageStopping);
314 internal_state.state = State::PackageStopped;
315
316 for sender in internal_state.package_shutdown_complete_callbacks.drain(..) {
317 let _ = sender.send(Ok(()));
318 }
319
320 if internal_state.received_boot_shutdown_signal {
321 internal_state.state = State::BootStopping;
322 true
323 } else {
324 false
325 }
326 };
327
328 if received_boot_shutdown_signal {
329 self.node_remover.shutdown_all_drivers().await;
330 self.on_boot_shutdown_complete().await;
331 }
332 }
333
334 async fn on_boot_shutdown_complete(&self) {
335 {
336 let mut internal_state = self.internal_state.borrow_mut();
337 assert_eq!(internal_state.state, State::BootStopping);
338 internal_state.state = State::Stopped;
339 }
340 self.execute_shutdown_strategy().await;
341 let mut internal_state = self.internal_state.borrow_mut();
342 for sender in internal_state.boot_shutdown_complete_callbacks.drain(..) {
343 let _ = sender.send(Ok(()));
344 }
345 }
346
347 async fn signal_package_shutdown(&self) -> Result<(), zx::Status> {
348 #![allow(clippy::await_holding_refcell_ref)]
352 let mut internal_state = self.internal_state.borrow_mut();
353
354 match internal_state.state {
355 State::Running | State::PackageStopping => {
356 let (tx, rx) = oneshot::channel();
357 internal_state.package_shutdown_complete_callbacks.push(tx);
358 if internal_state.state == State::Running {
359 internal_state.state = State::PackageStopping;
360 drop(internal_state);
361 self.node_remover.shutdown_pkg_drivers().await;
362 self.on_package_shutdown_complete().await;
363 } else {
364 drop(internal_state);
365 }
366 rx.await.unwrap_or(Err(zx::Status::INTERNAL))
367 }
368 _ => Ok(()),
369 }
370 }
371
372 async fn signal_boot_shutdown(&self) -> Result<(), zx::Status> {
373 #![allow(clippy::await_holding_refcell_ref)]
375 let mut internal_state = self.internal_state.borrow_mut();
376
377 if internal_state.state == State::Stopped {
378 return Ok(());
379 }
380
381 let (tx, rx) = oneshot::channel();
382 internal_state.boot_shutdown_complete_callbacks.push(tx);
383
384 internal_state.received_boot_shutdown_signal = true;
385 let state = internal_state.state;
386 match state {
387 State::Running | State::PackageStopped => {
388 internal_state.state = State::BootStopping;
389 drop(internal_state);
390
391 self.node_remover.shutdown_all_drivers().await;
392 self.on_boot_shutdown_complete().await;
393 }
394 State::BootStopping => {
395 error!("SignalBootShutdown() called during shutdown.");
396 }
397 _ => {}
398 }
399 rx.await.unwrap_or(Err(zx::Status::INTERNAL))
400 }
401
402 async fn execute_shutdown_strategy(&self) {
403 if !self.system_power.has_resources() {
404 warn!("Invalid Power resource. Assuming test.");
405 let internal_state = self.internal_state.borrow();
406 if internal_state.lifecycle_stop {
407 info!("Exiting driver manager gracefully");
408 self.system_power.exit(0);
409 }
410 return;
411 }
412
413 let shutdown_system_state = self.system_power.get_system_power_state().await;
414 info!("Suspend fallback with flags {:?}", shutdown_system_state);
415 let what = "zx_system_powerctl";
416
417 info!("Flushing logs.");
418 if let Some(log_flush) = &self.log_flush
419 && let Err(e) = log_flush.wait_until_flushed().await
420 {
421 warn!("Failed to flush logs: {}", e);
422 }
423
424 info!("Executing powerctl.");
425 let status = match shutdown_system_state {
426 fsystem_state::SystemPowerState::Reboot => {
427 self.system_power.system_powerctl(ZX_SYSTEM_POWERCTL_REBOOT)
428 }
429 fsystem_state::SystemPowerState::RebootBootloader => {
430 self.system_power.system_powerctl(ZX_SYSTEM_POWERCTL_REBOOT_BOOTLOADER)
431 }
432 fsystem_state::SystemPowerState::RebootRecovery => {
433 self.system_power.system_powerctl(ZX_SYSTEM_POWERCTL_REBOOT_RECOVERY)
434 }
435 fsystem_state::SystemPowerState::RebootKernelInitiated => {
436 let status = self
437 .system_power
438 .system_powerctl(ZX_SYSTEM_POWERCTL_ACK_KERNEL_INITIATED_REBOOT);
439 if status.is_ok() {
440 loop {
442 fasync::Timer::new(std::time::Duration::from_secs(5 * 60)).await;
443 println!(
444 "driver_manager: unexpectedly still running after successful reboot syscall"
445 );
446 }
447 }
448 status
449 }
450 fsystem_state::SystemPowerState::Poweroff => {
451 self.system_power.system_powerctl(ZX_SYSTEM_POWERCTL_SHUTDOWN)
452 }
453 fsystem_state::SystemPowerState::FullyOn => {
454 error!("Unexpected shutdown state requested: {:?}", shutdown_system_state);
455 Err(zx::Status::INVALID_ARGS)
456 }
457 };
458
459 let internal_state = self.internal_state.borrow();
460 if internal_state.lifecycle_stop {
461 info!("Exiting driver manager gracefully");
462 self.system_power.exit(0);
463 }
464
465 warn!("{}: {status:?}", what);
466 }
467}
468
469#[cfg(test)]
470mod tests {
471 use super::*;
472
473 struct MockNodeRemover {
474 timeout_callback: RefCell<Option<Box<dyn Fn()>>>,
475 shutdown_all_tx: RefCell<Option<oneshot::Sender<()>>>,
476 received_shutdown_all_request_tx: RefCell<Option<oneshot::Sender<()>>>,
477 received_shutdown_all_request_rx: RefCell<Option<oneshot::Receiver<()>>>,
478 }
479
480 impl MockNodeRemover {
481 fn new() -> Self {
482 let (tx, rx) = oneshot::channel();
483 Self {
484 timeout_callback: RefCell::new(None),
485 shutdown_all_tx: RefCell::new(None),
486 received_shutdown_all_request_tx: RefCell::new(Some(tx)),
487 received_shutdown_all_request_rx: RefCell::new(Some(rx)),
488 }
489 }
490
491 fn complete_shutdown_all(&self) {
492 if let Some(tx) = self.shutdown_all_tx.borrow_mut().take() {
493 let _ = tx.send(());
494 }
495 }
496
497 fn timeout_node_removal(&self) {
498 if let Some(cb) = self.timeout_callback.borrow().as_ref() {
499 cb();
500 }
501 }
502
503 async fn wait_for_shutdown_all_request(&self) {
504 let rx = self.received_shutdown_all_request_rx.borrow_mut().take().unwrap();
505 let _ = rx.await;
506 }
507 }
508
509 #[async_trait(?Send)]
510 impl NodeRemover for MockNodeRemover {
511 async fn shutdown_all_drivers(&self) {
512 if let Some(tx) = self.received_shutdown_all_request_tx.borrow_mut().take() {
513 let _ = tx.send(());
514 }
515 let (tx, rx) = oneshot::channel();
516 *self.shutdown_all_tx.borrow_mut() = Some(tx);
517 let _ = rx.await;
518 }
519 async fn shutdown_pkg_drivers(&self) {}
520 fn set_on_removal_timeout_callback(&self, callback: Box<dyn Fn()>) {
521 *self.timeout_callback.borrow_mut() = Some(callback);
522 }
523 }
524
525 struct MockSystemPower {
526 state: fsystem_state::SystemPowerState,
527 system_powerctl_called: RefCell<bool>,
528 system_powerctl_cmd: RefCell<Option<u32>>,
529 system_powerctl_tx: RefCell<Option<oneshot::Sender<()>>>,
530 system_powerctl_rx: RefCell<Option<oneshot::Receiver<()>>>,
531 exit_called: RefCell<bool>,
532 exit_tx: RefCell<Option<oneshot::Sender<()>>>,
533 exit_rx: RefCell<Option<oneshot::Receiver<()>>>,
534 }
535
536 impl MockSystemPower {
537 fn new(state: fsystem_state::SystemPowerState) -> Self {
538 let (p_tx, p_rx) = oneshot::channel();
539 let (e_tx, e_rx) = oneshot::channel();
540 Self {
541 state,
542 system_powerctl_called: RefCell::new(false),
543 system_powerctl_cmd: RefCell::new(None),
544 system_powerctl_tx: RefCell::new(Some(p_tx)),
545 system_powerctl_rx: RefCell::new(Some(p_rx)),
546 exit_called: RefCell::new(false),
547 exit_tx: RefCell::new(Some(e_tx)),
548 exit_rx: RefCell::new(Some(e_rx)),
549 }
550 }
551
552 async fn wait_for_system_powerctl(&self) {
554 let rx = self.system_powerctl_rx.borrow_mut().take().unwrap();
555 let _ = rx.await;
556 }
557
558 async fn wait_for_exit(&self) {
560 let rx = self.exit_rx.borrow_mut().take().unwrap();
561 let _ = rx.await;
562 }
563 }
564
565 #[async_trait(?Send)]
566 impl SystemPower for MockSystemPower {
567 async fn get_system_power_state(&self) -> fsystem_state::SystemPowerState {
568 self.state
569 }
570
571 fn system_powerctl(&self, cmd: u32) -> Result<(), zx::Status> {
572 *self.system_powerctl_called.borrow_mut() = true;
573 *self.system_powerctl_cmd.borrow_mut() = Some(cmd);
574 if let Some(tx) = self.system_powerctl_tx.borrow_mut().take() {
575 let _ = tx.send(());
576 }
577 Ok(())
578 }
579
580 fn has_resources(&self) -> bool {
581 true
582 }
583
584 fn exit(&self, _status: i32) {
585 *self.exit_called.borrow_mut() = true;
586 if let Some(tx) = self.exit_tx.borrow_mut().take() {
587 let _ = tx.send(());
588 }
589 }
590 }
591
592 struct FakeLogFlusher {
593 flushed: RefCell<bool>,
594 }
595
596 impl FakeLogFlusher {
597 fn new() -> Self {
598 Self { flushed: RefCell::new(false) }
599 }
600 }
601
602 async fn serve_log_flusher(
603 fake_log_flusher: Rc<FakeLogFlusher>,
604 mut stream: fdiagnostics::LogFlusherRequestStream,
605 ) -> Result<(), fidl::Error> {
606 while let Some(request) = stream.try_next().await? {
607 if let fdiagnostics::LogFlusherRequest::WaitUntilFlushed { responder } = request {
608 *fake_log_flusher.flushed.borrow_mut() = true;
609 responder.send()?;
610 }
611 }
612 Ok(())
613 }
614
615 fn setup_log_flusher(
616 scope: &fasync::Scope,
617 ) -> (fdiagnostics::LogFlusherProxy, Rc<FakeLogFlusher>) {
618 let (proxy, stream) = fidl::endpoints::create_proxy::<fdiagnostics::LogFlusherMarker>();
619 let fake = Rc::new(FakeLogFlusher::new());
620 let fake_clone = fake.clone();
621 scope.spawn_local(async move {
622 if let Err(e) = serve_log_flusher(fake_clone, stream.into_stream()).await {
623 error!("Error serving LogFlusher: {}", e);
624 }
625 });
626 (proxy, fake)
627 }
628
629 async fn run_shutdown_manager_test<F, Fut>(state: fsystem_state::SystemPowerState, test: F)
630 where
631 F: FnOnce(flifecycle::LifecycleProxy, Rc<MockNodeRemover>, Rc<MockSystemPower>) -> Fut,
632 Fut: std::future::Future<Output = ()>,
633 {
634 let node_remover = Rc::new(MockNodeRemover::new());
635 let system_power = Rc::new(MockSystemPower::new(state));
636
637 let scope = fasync::Scope::new();
638 let (log_flush_proxy, fake_log_flusher) = setup_log_flusher(&scope);
639
640 let shutdown_manager =
641 ShutdownManager::new(node_remover.clone(), system_power.clone(), Some(log_flush_proxy));
642
643 let (lifecycle_proxy, stream) =
644 fidl::endpoints::create_proxy::<flifecycle::LifecycleMarker>();
645 let (tx, rx) = oneshot::channel::<ShutdownSender>();
646
647 let self_clone = shutdown_manager.clone();
648 shutdown_manager.scope.spawn_local(async move {
649 if let Ok(sender) = rx.await {
650 self_clone.internal_state.borrow_mut().lifecycle_stop = true;
651 let status = self_clone.signal_boot_shutdown().await;
652 let _ = sender.send(status);
653 }
654 });
655 let lifecycle_server = Rc::new(LifecycleServer::new(tx));
656 shutdown_manager.scope.spawn_local(async move {
657 if let Err(e) = lifecycle_server.serve(stream.into_stream()).await {
658 error!("Error serving Lifecycle in test: {}", e);
659 }
660 });
661
662 test(lifecycle_proxy, node_remover, system_power).await;
663
664 assert!(*fake_log_flusher.flushed.borrow());
665 }
666
667 #[fuchsia::test]
669 async fn test_reboot_timeout() {
670 run_shutdown_manager_test(
671 fsystem_state::SystemPowerState::Reboot,
672 |lifecycle_proxy, node_remover, system_power| async move {
673 lifecycle_proxy.stop().unwrap();
675
676 node_remover.wait_for_shutdown_all_request().await;
678
679 node_remover.timeout_node_removal();
681
682 system_power.wait_for_system_powerctl().await;
684
685 system_power.wait_for_exit().await;
687
688 assert!(*system_power.system_powerctl_called.borrow());
690 assert_eq!(
691 Some(ZX_SYSTEM_POWERCTL_REBOOT),
692 *system_power.system_powerctl_cmd.borrow()
693 );
694 assert!(*system_power.exit_called.borrow());
695 },
696 )
697 .await;
698 }
699
700 #[fuchsia::test]
703 async fn test_reboot_normal() {
704 run_shutdown_manager_test(
705 fsystem_state::SystemPowerState::Reboot,
706 |lifecycle_proxy, node_remover, system_power| async move {
707 lifecycle_proxy.stop().unwrap();
709
710 node_remover.wait_for_shutdown_all_request().await;
712
713 node_remover.complete_shutdown_all();
715
716 system_power.wait_for_system_powerctl().await;
718
719 system_power.wait_for_exit().await;
721
722 assert!(*system_power.system_powerctl_called.borrow());
724 assert_eq!(
725 Some(ZX_SYSTEM_POWERCTL_REBOOT),
726 *system_power.system_powerctl_cmd.borrow()
727 );
728 assert!(*system_power.exit_called.borrow());
729 },
730 )
731 .await;
732 }
733}