/* StarForth — Steady-State Virtual Machine Runtime Copyright (c) 2023–2026 Robert A. James All rights reserved. This file is part of the StarForth project. Licensed under the Starship License, Version 1.0 (SL-1.0) (the "License"); you may not use this file except in compliance with the License. */ /** * vm_log_buffer.c - a VM's log records, on their way to Artemis's log ring. * See the header for the contract (FABRIC-3.md §XXXVIII, 2026-10-14). */ #ifdef __STARKERNEL__ #include "starkernel/vm_log_buffer.h" #include "vm.h" #include "log.h" /* LOG_ERROR, LOG_WARN */ #include "starkernel/vm/kernel_hermes.h" /* sk_hermes_send_one(), SK_HERMES_MSG_TYPE_LOG_BATCH */ #include "starkernel/capsule_birth.h" /* capsule_vm_find_by_name_nocase(), capsule_vm_registry_get() */ #include "starkernel/log_region.h" /* log_region_append() */ #include "starkernel/console.h" /* console_println() -- never log_message() here */ #include "freestanding/stdio.h" /* snprintf() */ #include #define REC_HEAD 11u /* level 1 + timestamp 8 + length 2 */ typedef char vm_log_buf_fits[(VM_LOG_BUF_SIZE + 64 <= SK_HERMES_CHUNK_MAX_PAYLOAD) ? 1 : -1]; /* Set while a record is being added or a buffer sent: a log line made from * inside that work (a kernel-Hermes refusal, say) must not re-enter it. */ static int g_busy; static uint32_t put_record(uint8_t *at, uint8_t level, uint64_t ts, const char *msg, uint16_t len) { at[0] = level; memcpy(at + 1, &ts, 8); memcpy(at + 9, &len, 2); memcpy(at + REC_HEAD, msg, len); return REC_HEAD + len; } static uint32_t count_records(const uint8_t *buf, uint32_t len) { uint32_t at = 0, n = 0; while (at + REC_HEAD <= len) { uint16_t l; memcpy(&l, buf + at + 9, 2); at += REC_HEAD + l; n++; } return n; } static int send_buffer(VM *vm) { uint8_t payload[SK_HERMES_CHUNK_MAX_PAYLOAD]; uint32_t len = 0, records = count_records(vm->log_buf, vm->log_buf_len); VMRegistryEntry artemis; if (vm->log_buf_len == 0 && vm->log_lost == 0) return 0; if (vm->log_lost > 0) { char note[64]; int n = snprintf(note, sizeof(note), "%u log records not sent: Artemis not ready", (unsigned)vm->log_lost); if (n > 0) len += put_record(payload, LOG_WARN, 0, note, (uint16_t)n); } memcpy(payload + len, vm->log_buf, vm->log_buf_len); len += vm->log_buf_len; if (capsule_vm_find_by_name_nocase("Artemis", &artemis) != 0 || !artemis.vm_ptr || (artemis.state != VM_STATE_LIVE && artemis.state != VM_STATE_SWITCHED_OUT) || sk_hermes_send_one(vm->stadium_vm_id, artemis.vm_id, SK_HERMES_MSG_TYPE_LOG_BATCH, 0, payload, len) != 0) { vm->log_lost += records; vm->log_buf_len = 0; return -1; } vm->log_lost = 0; vm->log_buf_len = 0; return (int)records; } void vm_log_buffer_add(VM *vm, uint8_t level, uint64_t timestamp, const char *msg, uint32_t len) { if (!vm || !msg) return; if (g_busy) { vm->log_lost++; return; } g_busy = 1; if (len > VM_LOG_BUF_SIZE - REC_HEAD) len = VM_LOG_BUF_SIZE - REC_HEAD; if (vm->log_buf_len + REC_HEAD + len > VM_LOG_BUF_SIZE) (void)send_buffer(vm); vm->log_buf_len = (uint16_t)(vm->log_buf_len + put_record(vm->log_buf + vm->log_buf_len, level, timestamp, msg, (uint16_t)len)); if (level == LOG_ERROR) (void)send_buffer(vm); g_busy = 0; } int vm_log_buffer_flush(VM *vm) { int rc; if (!vm || g_busy) return -1; g_busy = 1; rc = send_buffer(vm); g_busy = 0; return rc; } int vm_log_batch_deliver(VM *vm, VMUuid from, const uint8_t *payload, uint32_t payload_len) { VMRegistryEntry artemis, sender; const char *source = "?"; uint32_t at = 0; int written = 0, failed = 0; if (capsule_vm_find_by_name_nocase("Artemis", &artemis) != 0 || artemis.vm_ptr != (void *)vm) { return -1; } /* The source is the sender the kernel stamped on the message: a VM * cannot write a record under another's name. */ if (capsule_vm_registry_get(from, &sender) == 0) source = sender.name; while (at + REC_HEAD <= payload_len) { uint8_t level = payload[at]; uint64_t ts; uint16_t len; memcpy(&ts, payload + at + 1, 8); memcpy(&len, payload + at + 9, 2); if (at + REC_HEAD + len > payload_len) break; /* malformed tail: stop */ if (log_region_append(level, ts, source, (uint32_t)strlen(source), (const char *)payload + at + REC_HEAD, len) < 0) { failed++; } else { written++; } at += REC_HEAD + len; } if (failed) console_println("Artemis: log ring write failed -- records not kept"); return written; } #endif /* __STARKERNEL__ */