| // 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; |
| } |