| // Copyright 2019 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 <lib/async/cpp/wait.h> |
| #include <zircon/process.h> |
| #include <zircon/syscalls.h> |
| #include <zircon/syscalls/log.h> |
| |
| #include <optional> |
| |
| #include "vc.h" |
| |
| vc_t* g_log_vc; |
| |
| static zx_koid_t proc_koid; |
| static std::optional<async::Wait> log_wait; |
| static async_dispatcher_t* log_dispatcher = nullptr; |
| |
| static void log_reader_cb(async_dispatcher_t* dispatcher, async::Wait* wait, |
| zx_status_t wait_status, const zx_packet_signal_t* signal); |
| |
| // This is the list for logs on displays other than the main display. |
| static struct list_node log_list = LIST_INITIAL_VALUE(log_list); |
| |
| void set_log_listener_active(bool active) { |
| if (active) { |
| log_wait->Begin(log_dispatcher); |
| } else { |
| log_wait->Cancel(); |
| } |
| } |
| |
| zx_status_t log_create_vc(vc_gfx_t* graphics, vc_t** vc_out) { |
| vc_t* vc; |
| zx_status_t status = vc_alloc(&vc, &color_schemes[kDefaultColorScheme]); |
| if (status != ZX_OK) { |
| return status; |
| } |
| |
| // Copy the log buffer into the new vc. |
| size_t textbuf_size = g_log_vc->rows * g_log_vc->columns * sizeof(*vc->text_buf); |
| memcpy(vc->text_buf, g_log_vc->text_buf, textbuf_size); |
| vc->cursor_x = g_log_vc->cursor_x; |
| vc->cursor_y = g_log_vc->cursor_y; |
| |
| // Set the new vc graphics and flush the text. |
| vc->active = true; |
| vc->graphics = graphics; |
| vc_attach_gfx(vc); |
| vc_full_repaint(vc); |
| |
| list_add_tail(&log_list, &vc->node); |
| *vc_out = vc; |
| return ZX_OK; |
| } |
| |
| void log_delete_vc(vc_t* vc) { |
| list_delete(&vc->node); |
| |
| vc_free(vc); |
| } |
| |
| int log_start(async_dispatcher_t* dispatcher, zx::debuglog read_only_debuglog, |
| const color_scheme_t* color_scheme) { |
| log_dispatcher = dispatcher; |
| // Create initial console for debug log. |
| if (vc_create(&g_log_vc, color_scheme) != ZX_OK) { |
| return -1; |
| } |
| snprintf(g_log_vc->title, sizeof(g_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; |
| } |
| |
| log_wait.emplace(read_only_debuglog.release(), ZX_LOG_READABLE, 0, log_reader_cb); |
| return 0; |
| } |
| |
| static void write_to_log(vc_t* vc, zx_log_record_t* rec) { |
| 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(vc, tmp, strlen(tmp), 0); |
| vc_write(vc, rec->data, rec->datalen, 0); |
| if ((rec->datalen == 0) || (rec->data[rec->datalen - 1] != '\n')) { |
| vc_write(vc, "\n", 1, 0); |
| } |
| } |
| |
| static void log_reader_cb(async_dispatcher_t* dispatcher, async::Wait* wait, |
| zx_status_t wait_status, const zx_packet_signal_t* signal) { |
| char buf[ZX_LOG_RECORD_MAX]; |
| zx_log_record_t* rec = (zx_log_record_t*)buf; |
| zx_status_t status; |
| for (;;) { |
| status = zx_debuglog_read(wait->object(), 0, rec, ZX_LOG_RECORD_MAX); |
| // zx_debuglog_read returns >0 for success. |
| if (status < 0) { |
| break; |
| } |
| // Don't print log messages from ourself. |
| if (rec->pid == proc_koid) { |
| continue; |
| } |
| write_to_log(g_log_vc, rec); |
| |
| vc_t* vc = NULL; |
| list_for_every_entry (&log_list, vc, vc_t, node) { write_to_log(vc, rec); } |
| } |
| |
| if (status != ZX_ERR_SHOULD_WAIT) { |
| const char* oops = "<<LOG ERROR>>\n"; |
| vc_write(g_log_vc, oops, strlen(oops), 0); |
| } |
| |
| // Queue up another read. |
| wait->Begin(dispatcher); |
| } |