blob: 4d28d367c97bda5ffd6e0849eb520dd577234a71 [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 <poll.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <launchpad/launchpad.h>
#include <zircon/device/pty.h>
#include <zircon/device/vfs.h>
#include <zircon/device/display.h>
#include <zircon/listnode.h>
#include <zircon/process.h>
#include <zircon/processargs.h>
#include <zircon/syscalls.h>
#include <zircon/syscalls/log.h>
#include <zircon/syscalls/object.h>
#include <fdio/io.h>
#include <fdio/util.h>
#include <fdio/watcher.h>
#include <port/port.h>
#include "vc.h"
port_t port;
static port_handler_t ownership_ph;
static port_handler_t log_ph;
static port_handler_t new_vc_ph;
static port_handler_t input_ph;
static int input_dir_fd;
static vc_t* log_vc;
static zx_koid_t proc_koid;
static int g_fb_fd = -1;
// remember whether the virtual console controls the display
bool g_vc_owns_display = true;
void vc_toggle_framebuffer() {
uint32_t n = g_vc_owns_display ? 1 : 0;
ioctl_display_set_owner(g_fb_fd, &n);
}
static zx_status_t log_reader_cb(port_handler_t* ph, zx_signals_t signals, uint32_t evt) {
char buf[ZX_LOG_RECORD_MAX];
zx_log_record_t* rec = (zx_log_record_t*)buf;
zx_status_t status;
for (;;) {
if ((status = zx_log_read(ph->handle, ZX_LOG_RECORD_MAX, rec, 0)) < 0) {
if (status == ZX_ERR_SHOULD_WAIT) {
return ZX_OK;
}
break;
}
// don't print log messages from ourself
if (rec->pid == proc_koid) {
continue;
}
char tmp[64];
snprintf(tmp, 64, "\033[32m%05d.%03d\033[39m] \033[31m%05" PRIu64 ".\033[36m%05" PRIu64 "\033[39m> ",
(int)(rec->timestamp / 1000000000ULL),
(int)((rec->timestamp / 1000000ULL) % 1000ULL),
rec->pid, rec->tid);
vc_write(log_vc, tmp, strlen(tmp), 0);
vc_write(log_vc, rec->data, rec->datalen, 0);
if ((rec->datalen == 0) ||
(rec->data[rec->datalen - 1] != '\n')) {
vc_write(log_vc, "\n", 1, 0);
}
}
const char* oops = "<<LOG ERROR>>\n";
vc_write(log_vc, oops, strlen(oops), 0);
return status;
}
static zx_status_t launch_shell(vc_t* vc, int fd, const char* cmd) {
const char* args[] = { "/boot/bin/sh", "-c", cmd };
launchpad_t* lp;
launchpad_create(zx_job_default(), "vc:sh", &lp);
launchpad_load_from_file(lp, args[0]);
launchpad_set_args(lp, cmd ? 3 : 1, args);
launchpad_transfer_fd(lp, fd, FDIO_FLAG_USE_FOR_STDIO | 0);
launchpad_clone(lp, LP_CLONE_FDIO_NAMESPACE | LP_CLONE_ENVIRON | LP_CLONE_DEFAULT_JOB);
const char* errmsg;
zx_status_t r;
if ((r = launchpad_go(lp, &vc->proc, &errmsg)) < 0) {
printf("vc: cannot spawn shell: %s: %d\n", errmsg, r);
}
return r;
}
static void session_destroy(vc_t* vc) {
if (vc->fd >= 0) {
port_fd_handler_done(&vc->fh);
// vc_destroy() closes the fd
}
if (vc->proc != ZX_HANDLE_INVALID) {
zx_task_kill(vc->proc);
}
vc_destroy(vc);
}
static zx_status_t session_io_cb(port_fd_handler_t* fh, unsigned pollevt, uint32_t evt) {
vc_t* vc = containerof(fh, vc_t, fh);
if (pollevt & POLLIN) {
char data[1024];
ssize_t r = read(vc->fd, data, sizeof(data));
if (r > 0) {
vc_write(vc, data, r, 0);
return ZX_OK;
}
}
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) {
goto fail;
}
if(launch_shell(vc, fd, NULL) < 0) {
goto fail;
}
return ZX_OK;
}
}
fail:
session_destroy(vc);
return ZX_ERR_STOP;
}
static zx_status_t session_create(vc_t** out, int* out_fd, bool make_active, bool special) {
int fd;
// The ptmx device can start later than these threads
int retry = 30;
while ((fd = open("/dev/misc/ptmx", O_RDWR | O_NONBLOCK)) < 0) {
if (--retry == 0) {
return ZX_ERR_IO;
}
usleep(100000);
}
int client_fd = openat(fd, "0", O_RDWR);
if (client_fd < 0) {
close(fd);
return ZX_ERR_IO;
}
vc_t* vc;
if (vc_create(&vc, special)) {
close(fd);
close(client_fd);
return ZX_ERR_INTERNAL;
}
zx_status_t r;
if ((r = port_fd_handler_init(&vc->fh, fd, POLLIN | POLLRDHUP | POLLHUP)) < 0) {
vc_destroy(vc);
close(fd);
close(client_fd);
return r;
}
vc->fd = fd;
if (make_active) {
vc_set_active(-1, vc);
}
pty_window_size_t wsz = {
.width = vc->columns,
.height = vc->rows,
};
ioctl_pty_set_window_size(fd, &wsz);
vc->fh.func = session_io_cb;
*out = vc;
*out_fd = client_fd;
return ZX_OK;
}
static void start_shell(bool make_active, const char* cmd) {
vc_t* vc;
int fd;
if (session_create(&vc, &fd, make_active, cmd != NULL) < 0) {
return;
}
vc->is_shell = true;
if (launch_shell(vc, fd, cmd) < 0) {
session_destroy(vc);
} else {
port_wait(&port, &vc->fh.ph);
}
}
static zx_status_t new_vc_cb(port_handler_t* ph, zx_signals_t signals, uint32_t evt) {
zx_handle_t h;
uint32_t dcount, hcount;
if (zx_channel_read(ph->handle, 0, NULL, &h, 0, 1, &dcount, &hcount) < 0) {
return ZX_OK;
}
if (hcount != 1) {
return ZX_OK;
}
vc_t* vc;
int fd;
if (session_create(&vc, &fd, true, false) < 0) {
zx_handle_close(h);
return ZX_OK;
}
zx_handle_t handles[FDIO_MAX_HANDLES];
uint32_t types[FDIO_MAX_HANDLES];
zx_status_t r = fdio_transfer_fd(fd, FDIO_FLAG_USE_FOR_STDIO | 0, handles, types);
if ((r != 2) || (zx_channel_write(h, 0, types, 2 * sizeof(uint32_t), handles, 2) < 0)) {
for (int n = 0; n < r; n++) {
zx_handle_close(handles[n]);
}
session_destroy(vc);
} else {
port_wait(&port, &vc->fh.ph);
}
zx_handle_close(h);
return ZX_OK;
}
static void input_dir_event(unsigned evt, const char* name) {
if ((evt != VFS_WATCH_EVT_EXISTING) && (evt != VFS_WATCH_EVT_ADDED)) {
return;
}
printf("vc: new input device /dev/class/input/%s\n", name);
int fd;
if ((fd = openat(input_dir_fd, name, O_RDONLY)) < 0) {
return;
}
new_input_device(fd, handle_key_press);
}
static zx_status_t input_cb(port_handler_t* ph, zx_signals_t signals, uint32_t evt) {
if (!(signals & ZX_CHANNEL_READABLE)) {
return ZX_ERR_STOP;
}
// Buffer contains events { Opcode, Len, Name[Len] }
// See zircon/device/vfs.h for more detail
// extra byte is for temporary NUL
uint8_t buf[VFS_WATCH_MSG_MAX + 1];
uint32_t len;
if (zx_channel_read(ph->handle, 0, buf, NULL, sizeof(buf) - 1, 0, &len, NULL) < 0) {
return ZX_ERR_STOP;
}
uint8_t* msg = buf;
while (len >= 2) {
uint8_t event = *msg++;
uint8_t namelen = *msg++;
if (len < (namelen + 2u)) {
break;
}
// add temporary nul
uint8_t tmp = msg[namelen];
msg[namelen] = 0;
input_dir_event(event, (char*) msg);
msg[namelen] = tmp;
msg += namelen;
len -= (namelen + 2u);
}
return ZX_OK;
}
static zx_status_t ownership_ph_cb(port_handler_t* ph, zx_signals_t signals, uint32_t evt) {
// If we owned it, we've been notified of losing it, or the other way 'round
g_vc_owns_display = !g_vc_owns_display;
// If we've gained it, repaint
// In both cases adjust waitfor to wait for the opposite
if (g_vc_owns_display) {
ph->waitfor = ZX_USER_SIGNAL_1;
if (g_active_vc) {
vc_gfx_invalidate_all(g_active_vc);
}
} else {
ph->waitfor = ZX_USER_SIGNAL_0;
}
return ZX_OK;
}
int main(int argc, char** argv) {
// NOTE: devmgr has getenv_bool. when more options are added, consider
// sharing that.
bool keep_log = false;
const char* value = getenv("virtcon.keep-log-visible");
if (value == NULL ||
((strcmp(value, "0") == 0) ||
(strcmp(value, "false") == 0) ||
(strcmp(value, "off") == 0))) {
keep_log = false;
} else {
keep_log = true;
}
const char* cmd = NULL;
while (argc > 1) {
if (!strcmp(argv[1], "--run")) {
if (argc > 2) {
argc--;
argv++;
cmd = argv[1];
printf("CMD: %s\n", cmd);
}
}
argc--;
argv++;
}
if (port_init(&port) < 0) {
return -1;
}
int fd;
for (;;) {
if ((fd = open("/dev/class/framebuffer/000/virtcon", O_RDWR)) >= 0) {
break;
}
usleep(100000);
}
if (vc_init_gfx(fd) < 0) {
return -1;
}
g_fb_fd = fd;
// create initial console for debug log
if (vc_create(&log_vc, false) != ZX_OK) {
return -1;
}
g_status_width = log_vc->columns;
snprintf(log_vc->title, sizeof(log_vc->title), "debuglog");
// Get our process koid so the log reader can
// filter out our own debug messages from the log
zx_info_handle_basic_t info;
if (zx_object_get_info(zx_process_self(), ZX_INFO_HANDLE_BASIC, &info,
sizeof(info), NULL, NULL) == ZX_OK) {
proc_koid = info.koid;
}
// TODO: receive from launching process
if (zx_log_create(ZX_LOG_FLAG_READABLE, &log_ph.handle) < 0) {
printf("vc log listener: cannot open log\n");
return -1;
}
log_ph.func = log_reader_cb;
log_ph.waitfor = ZX_LOG_READABLE;
port_wait(&port, &log_ph);
if ((new_vc_ph.handle = zx_get_startup_handle(PA_HND(PA_USER0, 0))) != ZX_HANDLE_INVALID) {
new_vc_ph.func = new_vc_cb;
new_vc_ph.waitfor = ZX_CHANNEL_READABLE;
port_wait(&port, &new_vc_ph);
}
if ((input_dir_fd = open("/dev/class/input", O_DIRECTORY | O_RDONLY)) >= 0) {
vfs_watch_dir_t wd;
wd.mask = VFS_WATCH_MASK_ALL;
wd.options = 0;
if (zx_channel_create(0, &wd.channel, &input_ph.handle) == ZX_OK) {
if ((ioctl_vfs_watch_dir(input_dir_fd, &wd)) == ZX_OK) {
input_ph.waitfor = ZX_CHANNEL_READABLE | ZX_CHANNEL_PEER_CLOSED;
input_ph.func = input_cb;
port_wait(&port, &input_ph);
} else {
zx_handle_close(wd.channel);
zx_handle_close(input_ph.handle);
close(input_dir_fd);
}
} else {
close(input_dir_fd);
}
}
setenv("TERM", "xterm", 1);
start_shell(keep_log ? false : true, cmd);
start_shell(false, NULL);
start_shell(false, NULL);
zx_handle_t e = ZX_HANDLE_INVALID;
ioctl_display_get_ownership_change_event(fd, &e);
if (e != ZX_HANDLE_INVALID) {
ownership_ph.func = ownership_ph_cb;
ownership_ph.handle = e;
ownership_ph.waitfor = ZX_USER_SIGNAL_1;
port_wait(&port, &ownership_ph);
}
zx_status_t r = port_dispatch(&port, ZX_TIME_INFINITE, false);
printf("vc: port failure: %d\n", r);
return -1;
}