blob: 9a32bdc63f2685b50dfffe8ace32737035fb8b2e [file] [log] [blame]
// 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;
}