blob: c5813be0fbcaf22007c0936c87d6f4251f12ce57 [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/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 <fs/managed_vfs.h>
#include <fs/pseudo_dir.h>
#include <fs/service.h>
#include <fs/vfs.h>
#include "src/sys/lib/stdout-to-debuglog/cpp/stdout-to-debuglog.h"
namespace fio = ::llcpp::fuchsia::io;
namespace power_fidl = llcpp::fuchsia::hardware::power;
namespace device_manager_fidl = llcpp::fuchsia::device::manager;
namespace sys2_fidl = llcpp::fuchsia::sys2;
// The amount of time that the shim will spend trying to connect to
// power_manager before giving up.
// TODO(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 StateControlAdminServer final : public power_fidl::statecontrol::Admin::Interface {
public:
StateControlAdminServer() {}
// 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);
// Implementation of the Suspend method from the FIDL protocol.
void Suspend(
power_fidl::statecontrol::SystemPowerState state,
power_fidl::statecontrol::Admin::Interface::SuspendCompleter::Sync completer) override;
void PowerFullyOn(
power_fidl::statecontrol::Admin::Interface::PowerFullyOnCompleter::Sync completer) override;
void Reboot(power_fidl::statecontrol::RebootReason reboot_reason,
power_fidl::statecontrol::Admin::Interface::RebootCompleter::Sync completer) override;
void RebootToBootloader(
power_fidl::statecontrol::Admin::Interface::RebootToBootloaderCompleter::Sync completer)
override;
void RebootToRecovery(power_fidl::statecontrol::Admin::Interface::RebootToRecoveryCompleter::Sync
completer) override;
void Poweroff(
power_fidl::statecontrol::Admin::Interface::PoweroffCompleter::Sync completer) override;
void Mexec(power_fidl::statecontrol::Admin::Interface::MexecCompleter::Sync completer) override;
void SuspendToRam(
power_fidl::statecontrol::Admin::Interface::SuspendToRamCompleter::Sync completer) override;
};
// 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(device_manager_fidl::SystemPowerState state) {
zx::channel local;
zx_status_t status =
connect_to_protocol(&device_manager_fidl::SystemStateTransition::Name[0], &local);
if (status != ZX_OK) {
fprintf(stderr, "[shutdown-shim]: error connecting to driver_manager\n");
return status;
}
auto system_state_transition_behavior_client =
device_manager_fidl::SystemStateTransition::SyncClient(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.
zx_status_t initiate_component_shutdown() {
zx::channel local;
zx_status_t status = connect_to_protocol(&sys2_fidl::SystemController::Name[0], &local);
if (status != ZX_OK) {
fprintf(stderr, "[shutdown-shim]: error connecting to component_manager\n");
return status;
}
auto system_controller_client = sys2_fidl::SystemController::SyncClient(std::move(local));
auto resp = system_controller_client.Shutdown();
return resp.status();
}
// 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.
zx_status_t drive_shutdown_manually(device_manager_fidl::SystemPowerState state) {
printf("[shutdown-shim]: driving shutdown manually\n");
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");
std::this_thread::sleep_for(MANUAL_SYSTEM_SHUTDOWN_TIMEOUT);
// If we're still running after that sleep shutdown should have finished by
// now. Exit with a non-zero exit code to force the system to restart.
fprintf(stderr, "[shutdown-shim]: we shouldn't still be running, crashing the system\n");
exit(1);
}
// 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.
void forward_command(
device_manager_fidl::SystemPowerState fallback_state,
fit::function<zx_status_t(power_fidl::statecontrol::Admin::SyncClient)> send_command) {
zx::channel local;
zx_status_t status =
connect_to_protocol_with_timeout(&power_fidl::statecontrol::Admin::Name[0], &local);
if (status != ZX_OK) {
printf("[shutdown-shim]: failed to connect to power_manager\n");
// Call send_command with an invalid channel. It will observe a transport
// issue, and inform the client of success because we're going to drive the
// shutdown ourselves.
send_command(power_fidl::statecontrol::Admin::SyncClient(zx::channel()));
drive_shutdown_manually(fallback_state);
return;
}
status = send_command(power_fidl::statecontrol::Admin::SyncClient(std::move(local)));
if (status != ZX_OK) {
printf("[shutdown-shim]: failed to forward command to power_manager\n");
drive_shutdown_manually(fallback_state);
}
}
void StateControlAdminServer::Suspend(
power_fidl::statecontrol::SystemPowerState state,
power_fidl::statecontrol::Admin::Interface::SuspendCompleter::Sync completer) {
// Note: `state` can be safely cast here because
// fuchsia.device.manager.SystemPowerState has the same definition as
// fuchsia.hardware.power.statecontrol.SystemPowerState.
auto driver_manager_state = device_manager_fidl::SystemPowerState(state);
forward_command(driver_manager_state,
[state, completer = completer.ToAsync()](auto admin_client) mutable {
auto resp = admin_client.Suspend(state);
if (resp.status() != ZX_OK) {
// Transport error when forwarding command, we'll take ownership of
// driving the shutdown so tell the client that the request was
// successful.
completer.ReplySuccess();
return resp.status();
} else if (resp->result.is_err()) {
completer.ReplyError(resp->result.err());
return ZX_OK;
} else {
completer.ReplySuccess();
return ZX_OK;
}
});
}
void StateControlAdminServer::PowerFullyOn(
power_fidl::statecontrol::Admin::Interface::PowerFullyOnCompleter::Sync completer) {
completer.ReplyError(ZX_ERR_NOT_SUPPORTED);
}
void StateControlAdminServer::Reboot(
power_fidl::statecontrol::RebootReason reboot_reason,
power_fidl::statecontrol::Admin::Interface::RebootCompleter::Sync completer) {
forward_command(device_manager_fidl::SystemPowerState::SYSTEM_POWER_STATE_REBOOT,
[reboot_reason, completer = completer.ToAsync()](auto admin_client) mutable {
auto resp = admin_client.Reboot(reboot_reason);
if (resp.status() != ZX_OK) {
// Transport error when forwarding command, we'll take ownership of
// driving the shutdown so tell the client that the request was
// successful.
completer.ReplySuccess();
return resp.status();
} else if (resp->result.is_err()) {
completer.ReplyError(resp->result.err());
return ZX_OK;
} else {
completer.ReplySuccess();
return ZX_OK;
}
});
}
void StateControlAdminServer::RebootToBootloader(
power_fidl::statecontrol::Admin::Interface::RebootToBootloaderCompleter::Sync completer) {
forward_command(device_manager_fidl::SystemPowerState::SYSTEM_POWER_STATE_REBOOT_BOOTLOADER,
[completer = completer.ToAsync()](auto admin_client) mutable {
auto resp = admin_client.RebootToBootloader();
if (resp.status() != ZX_OK) {
// Transport error when forwarding command, we'll take ownership of
// driving the shutdown so tell the client that the request was
// successful.
completer.ReplySuccess();
return resp.status();
} else if (resp->result.is_err()) {
completer.ReplyError(resp->result.err());
return ZX_OK;
} else {
completer.ReplySuccess();
return ZX_OK;
}
});
}
void StateControlAdminServer::RebootToRecovery(
power_fidl::statecontrol::Admin::Interface::RebootToRecoveryCompleter::Sync completer) {
forward_command(device_manager_fidl::SystemPowerState::SYSTEM_POWER_STATE_REBOOT_RECOVERY,
[completer = completer.ToAsync()](auto admin_client) mutable {
auto resp = admin_client.RebootToRecovery();
if (resp.status() != ZX_OK) {
// Transport error when forwarding command, we'll take ownership of
// driving the shutdown so tell the client that the request was
// successful.
completer.ReplySuccess();
return resp.status();
} else if (resp->result.is_err()) {
completer.ReplyError(resp->result.err());
return ZX_OK;
} else {
completer.ReplySuccess();
return ZX_OK;
}
});
}
void StateControlAdminServer::Poweroff(
power_fidl::statecontrol::Admin::Interface::PoweroffCompleter::Sync completer) {
forward_command(device_manager_fidl::SystemPowerState::SYSTEM_POWER_STATE_POWEROFF,
[completer = completer.ToAsync()](auto admin_client) mutable {
auto resp = admin_client.Poweroff();
if (resp.status() != ZX_OK) {
// Transport error when forwarding command, we'll take ownership of
// driving the shutdown so tell the client that the request was
// successful.
completer.ReplySuccess();
return resp.status();
} else if (resp->result.is_err()) {
completer.ReplyError(resp->result.err());
return ZX_OK;
} else {
completer.ReplySuccess();
return ZX_OK;
}
});
}
void StateControlAdminServer::Mexec(
power_fidl::statecontrol::Admin::Interface::MexecCompleter::Sync completer) {
forward_command(device_manager_fidl::SystemPowerState::SYSTEM_POWER_STATE_MEXEC,
[completer = completer.ToAsync()](auto admin_client) mutable {
auto resp = admin_client.Mexec();
if (resp.status() != ZX_OK) {
// Transport error when forwarding command, we'll take ownership of
// driving the shutdown so tell the client that the request was
// successful.
completer.ReplySuccess();
return resp.status();
} else if (resp->result.is_err()) {
completer.ReplyError(resp->result.err());
return ZX_OK;
} else {
completer.ReplySuccess();
return ZX_OK;
}
});
}
void StateControlAdminServer::SuspendToRam(
power_fidl::statecontrol::Admin::Interface::SuspendToRamCompleter::Sync completer) {
forward_command(device_manager_fidl::SystemPowerState::SYSTEM_POWER_STATE_SUSPEND_RAM,
[completer = completer.ToAsync()](auto admin_client) mutable {
auto resp = admin_client.SuspendToRam();
if (resp.status() != ZX_OK) {
// Transport error when forwarding command, we'll take ownership of
// driving the shutdown so tell the client that the request was
// successful.
completer.ReplySuccess();
return resp.status();
} else if (resp->result.is_err()) {
completer.ReplyError(resp->result.err());
return ZX_OK;
} else {
completer.ReplySuccess();
return ZX_OK;
}
});
}
fbl::RefPtr<fs::Service> StateControlAdminServer::Create(async_dispatcher* dispatcher) {
return fbl::MakeRefCounted<fs::Service>([dispatcher](zx::channel chan) mutable {
zx_status_t status =
fidl::Bind(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(power_fidl::statecontrol::Admin::Name,
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;
}