| // Copyright 2020 The Fuchsia Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include <fuchsia/device/manager/llcpp/fidl.h> |
| #include <fuchsia/hardware/power/statecontrol/llcpp/fidl.h> |
| #include <fuchsia/io/llcpp/fidl.h> |
| #include <fuchsia/process/lifecycle/llcpp/fidl.h> |
| #include <fuchsia/sys2/llcpp/fidl.h> |
| #include <lib/async-loop/cpp/loop.h> |
| #include <lib/async-loop/default.h> |
| #include <lib/fdio/directory.h> |
| #include <lib/fidl-async/cpp/bind.h> |
| #include <lib/fit/function.h> |
| #include <zircon/process.h> |
| #include <zircon/status.h> |
| |
| #include <chrono> |
| #include <thread> |
| |
| #include <fbl/string_printf.h> |
| |
| #include "src/lib/storage/vfs/cpp/managed_vfs.h" |
| #include "src/lib/storage/vfs/cpp/pseudo_dir.h" |
| #include "src/lib/storage/vfs/cpp/service.h" |
| #include "src/lib/storage/vfs/cpp/vfs.h" |
| #include "src/sys/lib/stdout-to-debuglog/cpp/stdout-to-debuglog.h" |
| |
| namespace fio = fuchsia_io; |
| |
| namespace statecontrol_fidl = fuchsia_hardware_power_statecontrol; |
| namespace device_manager_fidl = fuchsia_device_manager; |
| namespace sys2_fidl = fuchsia_sys2; |
| |
| // The amount of time that the shim will spend trying to connect to |
| // power_manager before giving up. |
| // TODO(fxbug.dev/54426): increase this timeout |
| const zx::duration SERVICE_CONNECTION_TIMEOUT = zx::sec(2); |
| |
| // The amount of time that the shim will spend waiting for a manually trigger |
| // system shutdown to finish before forcefully restarting the system. |
| const std::chrono::duration MANUAL_SYSTEM_SHUTDOWN_TIMEOUT = std::chrono::minutes(60); |
| |
| class LifecycleServer final : public fidl::WireInterface<fuchsia_process_lifecycle::Lifecycle> { |
| public: |
| LifecycleServer( |
| fidl::WireInterface<statecontrol_fidl::Admin>::MexecCompleter::Async mexec_completer) |
| : mexec_completer_(std::move(mexec_completer)) {} |
| |
| static zx_status_t Create( |
| async_dispatcher_t* dispatcher, |
| fidl::WireInterface<statecontrol_fidl::Admin>::MexecCompleter::Async completer, |
| zx::channel chan); |
| |
| void Stop(StopCompleter::Sync& completer) override; |
| |
| private: |
| fidl::WireInterface<statecontrol_fidl::Admin>::MexecCompleter::Async mexec_completer_; |
| }; |
| |
| zx_status_t LifecycleServer::Create( |
| async_dispatcher_t* dispatcher, |
| fidl::WireInterface<statecontrol_fidl::Admin>::MexecCompleter::Async completer, |
| zx::channel chan) { |
| zx_status_t status = fidl::BindSingleInFlightOnly( |
| dispatcher, std::move(chan), std::make_unique<LifecycleServer>(std::move(completer))); |
| if (status != ZX_OK) { |
| fprintf(stderr, "[shutdown-shim]: failed to bind lifecycle service: %s\n", |
| zx_status_get_string(status)); |
| return status; |
| } |
| return ZX_OK; |
| } |
| |
| void LifecycleServer::Stop(StopCompleter::Sync& completer) { |
| printf( |
| "[shutdown-shim]: received shutdown command over lifecycle interface, completing the mexec " |
| "call\n"); |
| mexec_completer_.ReplySuccess(); |
| } |
| |
| class StateControlAdminServer final : public fidl::WireInterface<statecontrol_fidl::Admin> { |
| public: |
| StateControlAdminServer() : lifecycle_loop_((&kAsyncLoopConfigNoAttachToCurrentThread)) {} |
| |
| // Creates a new fs::Service backed by a new StateControlAdminServer, to be |
| // inserted into a pseudo fs. |
| static fbl::RefPtr<fs::Service> Create(async_dispatcher* dispatcher); |
| |
| void PowerFullyOn(fidl::WireInterface<statecontrol_fidl::Admin>::PowerFullyOnCompleter::Sync& |
| completer) override; |
| |
| void Reboot( |
| statecontrol_fidl::wire::RebootReason reboot_reason, |
| fidl::WireInterface<statecontrol_fidl::Admin>::RebootCompleter::Sync& completer) override; |
| |
| void RebootToBootloader( |
| fidl::WireInterface<statecontrol_fidl::Admin>::RebootToBootloaderCompleter::Sync& completer) |
| override; |
| |
| void RebootToRecovery( |
| fidl::WireInterface<statecontrol_fidl::Admin>::RebootToRecoveryCompleter::Sync& completer) |
| override; |
| |
| void Poweroff( |
| fidl::WireInterface<statecontrol_fidl::Admin>::PoweroffCompleter::Sync& completer) override; |
| |
| void Mexec( |
| fidl::WireInterface<statecontrol_fidl::Admin>::MexecCompleter::Sync& completer) override; |
| |
| void SuspendToRam(fidl::WireInterface<statecontrol_fidl::Admin>::SuspendToRamCompleter::Sync& |
| completer) override; |
| |
| private: |
| async::Loop lifecycle_loop_; |
| }; |
| |
| // Asynchronously connects to the given protocol. |
| zx_status_t connect_to_protocol(const char* name, zx::channel* local) { |
| zx::channel remote; |
| zx_status_t status = zx::channel::create(0, local, &remote); |
| if (status != ZX_OK) { |
| fprintf(stderr, "[shutdown-shim]: error creating channel: %s\n", zx_status_get_string(status)); |
| return status; |
| } |
| auto path = fbl::StringPrintf("/svc/%s", name); |
| status = fdio_service_connect(path.data(), remote.release()); |
| if (status != ZX_OK) { |
| printf("[shutdown-shim]: failed to connect to %s: %s\n", name, zx_status_get_string(status)); |
| return status; |
| } |
| return ZX_OK; |
| } |
| |
| // Opens a service node, failing if the provider of the service does not respond |
| // to messages within SERVICE_CONNECTION_TIMEOUT. |
| // |
| // This is accomplished by opening the service node, writing an invalid message |
| // to the channel, and observing PEER_CLOSED within the timeout. This is testing |
| // that something is responding to open requests for this service, as opposed to |
| // the intended provider for this service being stuck on component resolution |
| // indefinitely, which causes connection attempts to the component to never |
| // succeed nor fail. By observing a PEER_CLOSED, we can ensure that the service |
| // provider received our message and threw it out (or the provider doesn't |
| // exist). Upon receiving the PEER_CLOSED, we then open a new connection and |
| // save it in `local`. |
| // |
| // This is protecting against packaged components being stuck in resolution for |
| // forever, which happens if pkgfs never starts up (this always happens on |
| // bringup). Once a component is able to be resolved, then all new service |
| // connections will either succeed or fail rather quickly. |
| zx_status_t connect_to_protocol_with_timeout(const char* name, zx::channel* local) { |
| zx::channel local_2; |
| zx_status_t status = connect_to_protocol(name, &local_2); |
| if (status != ZX_OK) { |
| return status; |
| } |
| |
| // We want to use the zx_channel_call syscall directly here, because there's |
| // no way to set the timeout field on the call using the FIDL bindings. |
| char garbage_data[6] = {0, 1, 2, 3, 4, 5}; |
| zx_channel_call_args_t call = { |
| // Bytes to send in the channel call |
| .wr_bytes = garbage_data, |
| // Handles to send in the channel call |
| .wr_handles = nullptr, |
| // Buffer to write received bytes into from the channel call |
| .rd_bytes = nullptr, |
| // Buffer to write received handles into from the channel call |
| .rd_handles = nullptr, |
| // Number of bytes to send |
| .wr_num_bytes = 6, |
| // Number of bytes we can receive |
| .rd_num_bytes = 0, |
| // Number of handles we can receive |
| .rd_num_handles = 0, |
| }; |
| uint32_t actual_bytes, actual_handles; |
| status = local_2.call(0, zx::deadline_after(SERVICE_CONNECTION_TIMEOUT), &call, &actual_bytes, |
| &actual_handles); |
| if (status == ZX_ERR_TIMED_OUT) { |
| fprintf(stderr, "[shutdown-shim]: timed out connecting to %s\n", name); |
| return status; |
| } |
| if (status != ZX_ERR_PEER_CLOSED) { |
| fprintf(stderr, "[shutdown-shim]: unexpected response from %s: %s\n", name, |
| zx_status_get_string(status)); |
| return status; |
| } |
| return connect_to_protocol(name, local); |
| } |
| |
| // Connect to fuchsia.device.manager.SystemStateTransition and set the |
| // termination state. |
| zx_status_t set_system_state_transition_behavior(statecontrol_fidl::wire::SystemPowerState state) { |
| zx::channel local; |
| zx_status_t status = connect_to_protocol( |
| fidl::DiscoverableProtocolName<device_manager_fidl::SystemStateTransition>, &local); |
| if (status != ZX_OK) { |
| fprintf(stderr, "[shutdown-shim]: error connecting to driver_manager\n"); |
| return status; |
| } |
| auto system_state_transition_behavior_client = |
| fidl::WireSyncClient<device_manager_fidl::SystemStateTransition>(std::move(local)); |
| |
| auto resp = system_state_transition_behavior_client.SetTerminationSystemState(state); |
| if (resp.status() != ZX_OK) { |
| fprintf(stderr, "[shutdown-shim]: transport error sending message to driver_manager: %s\n", |
| resp.error()); |
| return resp.status(); |
| } |
| if (resp->result.is_err()) { |
| return resp->result.err(); |
| } |
| return ZX_OK; |
| } |
| |
| // Connect to fuchsia.sys2.SystemController and initiate a system shutdown. If |
| // everything goes well, this function shouldn't return until shutdown is |
| // complete. |
| zx_status_t initiate_component_shutdown() { |
| zx::channel local; |
| zx_status_t status = |
| connect_to_protocol(fidl::DiscoverableProtocolName<sys2_fidl::SystemController>, &local); |
| if (status != ZX_OK) { |
| fprintf(stderr, "[shutdown-shim]: error connecting to component_manager\n"); |
| return status; |
| } |
| auto system_controller_client = |
| fidl::WireSyncClient<sys2_fidl::SystemController>(std::move(local)); |
| |
| printf("[shutdown-shim]: calling system_controller_client.Shutdown()\n"); |
| auto resp = system_controller_client.Shutdown(); |
| printf("[shutdown-shim]: status was returned: %s\n", zx_status_get_string(status)); |
| return resp.status(); |
| } |
| |
| // Sleeps for MANUAL_SYSTEM_SHUTDOWN_TIMEOUT, and then exits the process |
| void shutdown_timer() { |
| std::this_thread::sleep_for(MANUAL_SYSTEM_SHUTDOWN_TIMEOUT); |
| |
| // We shouldn't still be running at this point |
| |
| exit(1); |
| } |
| |
| // Manually drive a shutdown by setting state as driver_manager's termination |
| // behavior and then instructing component_manager to perform an orderly |
| // shutdown of components. If the orderly shutdown takes too long the shim will |
| // exit with a non-zero exit code, killing the root job. |
| void drive_shutdown_manually(statecontrol_fidl::wire::SystemPowerState state) { |
| printf("[shutdown-shim]: driving shutdown manually\n"); |
| |
| // Start a new thread that makes us exit uncleanly after a timeout. This will |
| // guarantee that shutdown doesn't take longer than |
| // MANUAL_SYSTEM_SHUTDOWN_TIMEOUT, because we're marked as critical to the |
| // root job and us exiting will bring down userspace and cause a reboot. |
| std::thread(shutdown_timer).detach(); |
| |
| zx_status_t status = set_system_state_transition_behavior(state); |
| if (status != ZX_OK) { |
| fprintf(stderr, |
| "[shutdown-shim]: error setting system state transition behavior in driver_manager, " |
| "proceeding with component shutdown anyway: %s\n", |
| zx_status_get_string(status)); |
| // Proceed here, maybe we can at least gracefully reboot still |
| // (driver_manager's default behavior) |
| } |
| |
| status = initiate_component_shutdown(); |
| if (status != ZX_OK) { |
| fprintf( |
| stderr, |
| "[shutdown-shim]: error initiating component shutdown, system shutdown impossible: %s\n", |
| zx_status_get_string(status)); |
| // Recovery from this state is impossible. Exit with a non-zero exit code, |
| // so our critical marking causes the system to forcefully restart. |
| exit(1); |
| } |
| fprintf(stderr, "[shutdown-shim]: manual shutdown successfully initiated\n"); |
| } |
| |
| zx_status_t send_command(fidl::WireSyncClient<statecontrol_fidl::Admin> statecontrol_client, |
| statecontrol_fidl::wire::SystemPowerState fallback_state, |
| statecontrol_fidl::wire::RebootReason* reboot_reason) { |
| switch (fallback_state) { |
| case statecontrol_fidl::wire::SystemPowerState::REBOOT: { |
| if (reboot_reason == nullptr) { |
| fprintf(stderr, "[shutdown-shim]: internal error, bad pointer to reason for reboot\n"); |
| return ZX_ERR_INTERNAL; |
| } |
| auto resp = statecontrol_client.Reboot(*reboot_reason); |
| if (resp.status() != ZX_OK) { |
| return ZX_ERR_UNAVAILABLE; |
| } else if (resp->result.is_err()) { |
| return resp->result.err(); |
| } else { |
| return ZX_OK; |
| } |
| } break; |
| case statecontrol_fidl::wire::SystemPowerState::REBOOT_BOOTLOADER: { |
| auto resp = statecontrol_client.RebootToBootloader(); |
| if (resp.status() != ZX_OK) { |
| return ZX_ERR_UNAVAILABLE; |
| } else if (resp->result.is_err()) { |
| return resp->result.err(); |
| } else { |
| return ZX_OK; |
| } |
| } break; |
| case statecontrol_fidl::wire::SystemPowerState::REBOOT_RECOVERY: { |
| auto resp = statecontrol_client.RebootToRecovery(); |
| if (resp.status() != ZX_OK) { |
| return ZX_ERR_UNAVAILABLE; |
| } else if (resp->result.is_err()) { |
| return resp->result.err(); |
| } else { |
| return ZX_OK; |
| } |
| } break; |
| case statecontrol_fidl::wire::SystemPowerState::POWEROFF: { |
| auto resp = statecontrol_client.Poweroff(); |
| if (resp.status() != ZX_OK) { |
| return ZX_ERR_UNAVAILABLE; |
| } else if (resp->result.is_err()) { |
| return resp->result.err(); |
| } else { |
| return ZX_OK; |
| } |
| } break; |
| case statecontrol_fidl::wire::SystemPowerState::MEXEC: { |
| auto resp = statecontrol_client.Mexec(); |
| if (resp.status() != ZX_OK) { |
| return ZX_ERR_UNAVAILABLE; |
| } else if (resp->result.is_err()) { |
| return resp->result.err(); |
| } else { |
| return ZX_OK; |
| } |
| } break; |
| case statecontrol_fidl::wire::SystemPowerState::SUSPEND_RAM: { |
| auto resp = statecontrol_client.SuspendToRam(); |
| if (resp.status() != ZX_OK) { |
| return ZX_ERR_UNAVAILABLE; |
| } else if (resp->result.is_err()) { |
| return resp->result.err(); |
| } else { |
| return ZX_OK; |
| } |
| } break; |
| default: |
| return ZX_ERR_INTERNAL; |
| } |
| } |
| |
| // Connects to power_manager and passes a SyncClient to the given function. The |
| // function is expected to return an error if there was a transport-related |
| // issue talking to power_manager, in which case this program will talk to |
| // driver_manager and component_manager to drive shutdown manually. |
| zx_status_t forward_command(statecontrol_fidl::wire::SystemPowerState fallback_state, |
| statecontrol_fidl::wire::RebootReason* reboot_reason) { |
| printf("[shutdown-shim]: checking power_manager liveness\n"); |
| zx::channel local; |
| zx_status_t status = connect_to_protocol_with_timeout( |
| fidl::DiscoverableProtocolName<statecontrol_fidl::Admin>, &local); |
| if (status == ZX_OK) { |
| printf("[shutdown-shim]: trying to forward command\n"); |
| status = send_command(fidl::WireSyncClient<statecontrol_fidl::Admin>(std::move(local)), |
| fallback_state, reboot_reason); |
| if (status != ZX_ERR_UNAVAILABLE) { |
| return status; |
| } |
| } |
| |
| printf("[shutdown-shim]: failed to forward command to power_manager: %s\n", |
| zx_status_get_string(status)); |
| |
| drive_shutdown_manually(fallback_state); |
| |
| // We should block on fuchsia.sys.SystemController forever on this thread, if |
| // it returns something has gone wrong. |
| fprintf(stderr, "[shutdown-shim]: we shouldn't still be running, crashing the system\n"); |
| exit(1); |
| } |
| |
| zx_status_t forward_command(statecontrol_fidl::wire::SystemPowerState fallback_state) { |
| return forward_command(fallback_state, nullptr); |
| } |
| |
| void StateControlAdminServer::PowerFullyOn( |
| fidl::WireInterface<statecontrol_fidl::Admin>::PowerFullyOnCompleter::Sync& completer) { |
| completer.ReplyError(ZX_ERR_NOT_SUPPORTED); |
| } |
| |
| void StateControlAdminServer::Reboot( |
| statecontrol_fidl::wire::RebootReason reboot_reason, |
| fidl::WireInterface<statecontrol_fidl::Admin>::RebootCompleter::Sync& completer) { |
| zx_status_t status = |
| forward_command(statecontrol_fidl::wire::SystemPowerState::REBOOT, &reboot_reason); |
| if (status == ZX_OK) { |
| completer.ReplySuccess(); |
| } else { |
| completer.ReplyError(status); |
| } |
| } |
| |
| void StateControlAdminServer::RebootToBootloader( |
| fidl::WireInterface<statecontrol_fidl::Admin>::RebootToBootloaderCompleter::Sync& completer) { |
| zx_status_t status = |
| forward_command(statecontrol_fidl::wire::SystemPowerState::REBOOT_BOOTLOADER); |
| if (status == ZX_OK) { |
| completer.ReplySuccess(); |
| } else { |
| completer.ReplyError(status); |
| } |
| } |
| |
| void StateControlAdminServer::RebootToRecovery( |
| fidl::WireInterface<statecontrol_fidl::Admin>::RebootToRecoveryCompleter::Sync& completer) { |
| zx_status_t status = forward_command(statecontrol_fidl::wire::SystemPowerState::REBOOT_RECOVERY); |
| if (status == ZX_OK) { |
| completer.ReplySuccess(); |
| } else { |
| completer.ReplyError(status); |
| } |
| } |
| |
| void StateControlAdminServer::Poweroff( |
| fidl::WireInterface<statecontrol_fidl::Admin>::PoweroffCompleter::Sync& completer) { |
| zx_status_t status = forward_command(statecontrol_fidl::wire::SystemPowerState::POWEROFF); |
| if (status == ZX_OK) { |
| completer.ReplySuccess(); |
| } else { |
| completer.ReplyError(status); |
| } |
| } |
| |
| void StateControlAdminServer::Mexec( |
| fidl::WireInterface<statecontrol_fidl::Admin>::MexecCompleter::Sync& completer) { |
| zx::channel local; |
| zx_status_t status = connect_to_protocol_with_timeout( |
| fidl::DiscoverableProtocolName<statecontrol_fidl::Admin>, &local); |
| if (status == ZX_OK) { |
| status = send_command(fidl::WireSyncClient<statecontrol_fidl::Admin>(std::move(local)), |
| statecontrol_fidl::wire::SystemPowerState::MEXEC, nullptr); |
| if (status == ZX_OK) { |
| completer.ReplySuccess(); |
| return; |
| } else if (status != ZX_ERR_UNAVAILABLE) { |
| completer.ReplyError(status); |
| return; |
| } |
| } |
| |
| printf("[shutdown-shim]: failed to forward mexec command to power_manager: %s\n", |
| zx_status_get_string(status)); |
| |
| // The mexec command will cause driver_manager to safely terminate, and _not_ |
| // turn the system off. This will result in shutdown progressing to the |
| // shutdown shim. Once it reaches us we know that all drivers and filesystems |
| // are parked, so we can return the mexec call, at which point the client will |
| // make the mexec syscall. |
| // |
| // Start a new lifecycle server with the completer so that it can respond to |
| // the client once we're told to terminate. Do this on a separate thread |
| // because this one will be blocked on the fuchsia.sys2.SystemController call. |
| zx::channel lifecycle_request(zx_take_startup_handle(PA_LIFECYCLE)); |
| if (!lifecycle_request.is_valid()) { |
| printf("[shutdown-shim]: missing lifecycle handle, mexec must have already been called\n"); |
| completer.ReplyError(ZX_ERR_INTERNAL); |
| return; |
| } |
| |
| status = LifecycleServer::Create(lifecycle_loop_.dispatcher(), completer.ToAsync(), |
| std::move(lifecycle_request)); |
| if (status != ZX_OK) { |
| fprintf(stderr, "[shutdown-shim]: failed to start lifecycle server: %d\n", status); |
| exit(status); |
| } |
| |
| lifecycle_loop_.StartThread("lifecycle"); |
| |
| drive_shutdown_manually(statecontrol_fidl::wire::SystemPowerState::MEXEC); |
| |
| // We should block on fuchsia.sys.SystemController forever on this thread, if |
| // it returns something has gone wrong. |
| fprintf(stderr, "[shutdown-shim]: we shouldn't still be running, crashing the system\n"); |
| exit(1); |
| } |
| |
| void StateControlAdminServer::SuspendToRam( |
| fidl::WireInterface<statecontrol_fidl::Admin>::SuspendToRamCompleter::Sync& completer) { |
| zx_status_t status = forward_command(statecontrol_fidl::wire::SystemPowerState::SUSPEND_RAM); |
| if (status == ZX_OK) { |
| completer.ReplySuccess(); |
| } else { |
| completer.ReplyError(status); |
| } |
| } |
| |
| fbl::RefPtr<fs::Service> StateControlAdminServer::Create(async_dispatcher* dispatcher) { |
| return fbl::MakeRefCounted<fs::Service>([dispatcher](zx::channel chan) mutable { |
| zx_status_t status = fidl::BindSingleInFlightOnly(dispatcher, std::move(chan), |
| std::make_unique<StateControlAdminServer>()); |
| if (status != ZX_OK) { |
| fprintf(stderr, "[shutdown-shim] failed to bind statecontrol.Admin service: %s\n", |
| zx_status_get_string(status)); |
| return status; |
| } |
| return ZX_OK; |
| }); |
| } |
| |
| int main() { |
| zx_status_t status = StdoutToDebuglog::Init(); |
| if (status != ZX_OK) { |
| return status; |
| } |
| printf("[shutdown-shim]: started\n"); |
| |
| async::Loop loop((async::Loop(&kAsyncLoopConfigAttachToCurrentThread))); |
| |
| fs::ManagedVfs outgoing_vfs((loop.dispatcher())); |
| auto outgoing_dir = fbl::MakeRefCounted<fs::PseudoDir>(); |
| auto svc_dir = fbl::MakeRefCounted<fs::PseudoDir>(); |
| |
| svc_dir->AddEntry(fidl::DiscoverableProtocolName<statecontrol_fidl::Admin>, |
| StateControlAdminServer::Create(loop.dispatcher())); |
| outgoing_dir->AddEntry("svc", std::move(svc_dir)); |
| |
| outgoing_vfs.ServeDirectory(outgoing_dir, |
| zx::channel(zx_take_startup_handle(PA_DIRECTORY_REQUEST))); |
| |
| loop.Run(); |
| |
| fprintf(stderr, "[shutdown-shim]: exited unexpectedly\n"); |
| return 1; |
| } |