blob: ccbb260895c5897d51c9a6a1a43221c358ad186f [file] [log] [blame]
// Copyright 2016 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 <assert.h>
#include <fcntl.h>
#include <fuchsia/hardware/pty/llcpp/fidl.h>
#include <fuchsia/io/c/fidl.h>
#include <fuchsia/virtualconsole/llcpp/fidl.h>
#include <lib/async-loop/cpp/loop.h>
#include <lib/fdio/directory.h>
#include <lib/fdio/fd.h>
#include <lib/fdio/fdio.h>
#include <lib/fdio/io.h>
#include <lib/fdio/spawn.h>
#include <lib/fdio/watcher.h>
#include <lib/svc/dir.h>
#include <lib/svc/outgoing.h>
#include <lib/zircon-internal/paths.h>
#include <lib/zx/channel.h>
#include <poll.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/ioctl.h>
#include <unistd.h>
#include <zircon/device/vfs.h>
#include <zircon/process.h>
#include <zircon/processargs.h>
#include <zircon/status.h>
#include <zircon/syscalls.h>
#include <zircon/syscalls/log.h>
#include <zircon/syscalls/object.h>
#include <iterator>
#include <memory>
#include <utility>
#include <fbl/algorithm.h>
#include <fbl/string_piece.h>
#include <fbl/unique_fd.h>
#include <fs/service.h>
#include <src/storage/deprecated-fs-fidl-handler/fidl-handler.h>
#include "args.h"
#include "keyboard.h"
#include "src/lib/listnode/listnode.h"
#include "vc.h"
namespace fpty = ::llcpp::fuchsia::hardware::pty;
static zx_status_t launch_shell(vc_t* vc, int fd, const char* cmd) {
const char* argv[] = {ZX_SHELL_DEFAULT, nullptr, nullptr, nullptr};
if (cmd) {
argv[1] = "-c";
argv[2] = cmd;
}
fdio_spawn_action_t actions[2] = {};
actions[0].action = FDIO_SPAWN_ACTION_SET_NAME;
actions[0].name.data = "vc:sh";
actions[1].action = FDIO_SPAWN_ACTION_TRANSFER_FD;
actions[1].fd = {.local_fd = fd, .target_fd = FDIO_FLAG_USE_FOR_STDIO};
uint32_t flags = FDIO_SPAWN_CLONE_ALL & ~FDIO_SPAWN_CLONE_STDIO;
char err_msg[FDIO_SPAWN_ERR_MSG_MAX_LENGTH];
zx_status_t status = fdio_spawn_etc(ZX_HANDLE_INVALID, flags, argv[0], argv, nullptr,
std::size(actions), actions, &vc->proc, err_msg);
if (status != ZX_OK) {
printf("vc: cannot spawn shell: %s: %d (%s)\n", err_msg, status, zx_status_get_string(status));
}
return status;
}
static void session_destroy(vc_t* vc) {
if (vc->io != nullptr) {
fdio_unsafe_release(vc->io);
}
if (vc->proc != ZX_HANDLE_INVALID) {
zx_task_kill(vc->proc);
}
// vc_destroy() closes the vc->fd.
vc_destroy(vc);
}
static void session_io_cb(vc_t* vc, async_dispatcher_t* dispatcher, async::Wait* wait,
zx_status_t status, const zx_packet_signal_t* signal) {
uint32_t pollevt = 0;
fdio_unsafe_wait_end(vc->io, signal->observed, &pollevt);
if (pollevt & POLLIN) {
char data[1024];
ssize_t r = read(vc->fd, data, sizeof(data));
if (r > 0) {
vc_write(vc, data, r, 0);
wait->Begin(dispatcher);
return;
}
}
if (pollevt & (POLLRDHUP | POLLHUP)) {
// shell sessions get restarted on exit
if (vc->is_shell) {
zx_task_kill(vc->proc);
vc->proc = ZX_HANDLE_INVALID;
int fd = openat(vc->fd, "0", O_RDWR);
if (fd < 0) {
session_destroy(vc);
return;
}
if (launch_shell(vc, fd, NULL) < 0) {
session_destroy(vc);
return;
}
wait->Begin(dispatcher);
return;
}
}
}
static zx_status_t remote_session_create(vc_t** out, zx::channel session, bool make_active,
const color_scheme_t* color_scheme) {
// Connect to the PTY service. We have to do this dance rather than just
// using open() because open() uses the DESCRIBE flag internally, and the
// plumbing of the PTY service through svchost causes the DESCRIBE to get
// consumed by the wrong code, resulting in the wrong NodeInfo being provided.
// This manifests as a loss of fd signals.
fbl::unique_fd fd;
{
zx::channel local, remote;
zx_status_t status = zx::channel::create(0, &local, &remote);
if (status != ZX_OK) {
return status;
}
status = fdio_service_connect("/svc/fuchsia.hardware.pty.Device", remote.release());
if (status != ZX_OK) {
return status;
}
int raw_fd;
status = fdio_fd_create(local.release(), &raw_fd);
if (status != ZX_OK) {
return status;
}
fd.reset(raw_fd);
int flags = fcntl(fd.get(), F_GETFL);
if (flags < 0) {
return ZX_ERR_IO;
}
if (fcntl(fd.get(), F_SETFL, flags | O_NONBLOCK) < 0) {
return ZX_ERR_IO;
}
}
fdio_t* io = fdio_unsafe_fd_to_io(fd.get());
if (io == nullptr) {
return ZX_ERR_INTERNAL;
}
auto result = fpty::Device::Call::OpenClient(zx::unowned_channel(fdio_unsafe_borrow_channel(io)),
0, std::move(session));
fdio_unsafe_release(io);
if (result.status() != ZX_OK) {
return result.status();
}
if (result->s != ZX_OK) {
return result->s;
}
vc_t* vc;
if (vc_create(&vc, color_scheme)) {
return ZX_ERR_INTERNAL;
}
zx_handle_t handle;
zx_signals_t signals;
fdio_unsafe_wait_begin(io, POLLIN | POLLRDHUP | POLLHUP, &handle, &signals);
vc->pty_wait =
std::make_unique<async::Wait>(handle, signals, 0,
[vc](async_dispatcher_t* dispatcher, async::Wait* wait,
zx_status_t status, const zx_packet_signal_t* signal) {
session_io_cb(vc, dispatcher, wait, status, signal);
});
struct winsize wsz = {};
wsz.ws_col = vc->columns;
wsz.ws_row = vc->rows;
ioctl(fd.get(), TIOCSWINSZ, &wsz);
vc->io = fdio_unsafe_fd_to_io(fd.get());
vc->fd = fd.release();
if (make_active) {
vc_set_active(-1, vc);
}
*out = vc;
return ZX_OK;
}
static zx_status_t session_create(vc_t** out, int* out_fd, bool make_active,
const color_scheme_t* color_scheme) {
zx::channel device_channel, client_channel;
zx_status_t status = zx::channel::create(0, &device_channel, &client_channel);
if (status != ZX_OK) {
return status;
}
status = remote_session_create(out, std::move(device_channel), make_active, color_scheme);
if (status != ZX_OK) {
return status;
}
int raw_client_fd;
status = fdio_fd_create(client_channel.release(), &raw_client_fd);
if (status != ZX_OK) {
return status;
}
fbl::unique_fd client_fd(raw_client_fd);
*out_fd = client_fd.release();
return ZX_OK;
}
static void start_shell(async_dispatcher_t* dispatcher, bool make_active, const char* cmd,
const color_scheme_t* color_scheme) {
vc_t* vc = nullptr;
int fd = 0;
if (session_create(&vc, &fd, make_active, color_scheme) < 0) {
return;
}
vc->is_shell = true;
if (launch_shell(vc, fd, cmd) < 0) {
session_destroy(vc);
} else {
vc->pty_wait->Begin(dispatcher);
}
}
class VirtconImpl final : public llcpp::fuchsia::virtualconsole::SessionManager::Interface {
public:
VirtconImpl(async_dispatcher_t* dispatcher, bool keep_log)
: dispatcher_(dispatcher), keep_log_(keep_log) {}
zx_status_t Bind(zx::channel request) {
auto result = fidl::BindServer(dispatcher_, std::move(request), this);
if (!result.is_ok()) {
return result.error();
}
return ZX_OK;
}
void CreateSession(::zx::channel session, CreateSessionCompleter::Sync& completer) override {
bool make_active = !(keep_log_ && g_active_vc && g_active_vc == g_log_vc && g_active_vc);
vc_t* vc = nullptr;
if (remote_session_create(&vc, std::move(session), make_active,
&color_schemes[kDefaultColorScheme]) < 0) {
completer.Reply(ZX_OK);
return;
}
vc->pty_wait->Begin(dispatcher_);
completer.Reply(ZX_OK);
}
void HasPrimaryConnected(HasPrimaryConnectedCompleter::Sync& completer) override {
completer.Reply(is_primary_bound());
}
private:
async_dispatcher_t* dispatcher_;
bool keep_log_;
};
int main(int argc, char** argv) {
llcpp::fuchsia::boot::Arguments::SyncClient boot_args;
{
zx::channel local, remote;
zx_status_t status = zx::channel::create(0, &local, &remote);
if (status != ZX_OK) {
return 1;
}
status = fdio_service_connect("/svc/fuchsia.boot.Arguments", remote.release());
if (status != ZX_OK) {
return 1;
}
boot_args = llcpp::fuchsia::boot::Arguments::SyncClient(std::move(local));
}
Arguments args;
zx_status_t status = ParseArgs(boot_args, &args);
if (status != ZX_OK) {
printf("vc: failed to get boot arguments\n");
return -1;
}
if (args.disable) {
printf("vc: virtcon disabled\n");
return 0;
}
if (!args.command.empty()) {
printf("vc: CMD: %s\n", args.command.c_str());
}
vc_device_init(args.font, args.keymap);
char* colorvar = getenv("virtcon.colorscheme");
const color_scheme_t* color_scheme = string_to_color_scheme(colorvar);
if (args.command.c_str() != NULL) {
color_scheme = &color_schemes[kSpecialColorScheme];
}
async::Loop loop = async::Loop(&kAsyncLoopConfigNeverAttachToThread);
VirtconImpl virtcon_server = VirtconImpl(loop.dispatcher(), args.keep_log_visible);
svc::Outgoing outgoing(loop.dispatcher());
status = outgoing.ServeFromStartupInfo();
if (status != ZX_OK) {
printf("vc: outgoing.ServeFromStartupInfo() = %s\n", zx_status_get_string(status));
return -1;
}
status = outgoing.svc_dir()->AddEntry(
llcpp::fuchsia::virtualconsole::SessionManager::Name,
fbl::MakeRefCounted<fs::Service>([&virtcon_server](zx::channel request) mutable {
zx_status_t status = virtcon_server.Bind(std::move(request));
if (status != ZX_OK) {
printf("vc: error binding new server: %d\n", status);
}
return status;
}));
if (log_start(loop.dispatcher()) < 0) {
return -1;
}
if (!args.repeat_keys) {
printf("vc: Key repeat disabled\n");
}
status = setup_keyboard_watcher(loop.dispatcher(), handle_key_press, args.repeat_keys);
if (status != ZX_OK) {
printf("vc: setup_keyboard_watcher failed with %d\n", status);
}
if (!vc_sysmem_connect()) {
return -1;
}
if (!vc_display_init(loop.dispatcher(), args.hide_on_boot)) {
return -1;
}
setenv("TERM", "xterm", 1);
for (size_t i = 0; i < args.shells; ++i) {
if (i == 0) {
const char* command = args.command.empty() ? nullptr : args.command.c_str();
start_shell(loop.dispatcher(), !args.keep_log_visible, command, args.color_scheme);
} else {
start_shell(loop.dispatcher(), false, nullptr, args.color_scheme);
}
}
status = loop.Run();
printf("vc: loop stopped: %d\n", status);
return -1;
}