Files
nt-lite/code/user/system/rssrv/console.c
T
maksw 73abd6f90d isolate POSIX builds from the host UCRT, wire lwext4 and ntfs-3g as ext4.sys and ntfs.sys, split the FAT driver into directory/file/async/driver/mount units, add posixsrv and dwm plus input/console/desktop syscall clients and a pile of new smoke targets, rename serial.sys to cereal.sys, name HDA/USB/lwIP/AHCI workers, harden ATA probing/reset/removal around actual hardware deadlines, absent-channel detection, diagnostics and explicit worker lifetime, redo ACPI S5 shutdown around GAS/MMIO and HW-reduced firmware, scan SSDTs for _S5 and deal with Intel SMI sleep trapping, make serial and SMP timeouts use real monotonic deadlines, add thread names and fix terminal thread teardown, scheduler and APC prepare/commit races, make advisory file locks queryable and preserve split ranges and add mutable file append state, add shared-memory pipes with kernel-assisted wait/wake, introduce capability-scoped input and display managers, split surface map rights into read/write, add explicit unmap and replace present with commit/generation/damage tracking while keeping physical presentation under the display manager, decouple native exception classes from Win32 exception numbers and translate them in kernel32, add opaque process startup data, bump the resource protocol to v3 with namespace directories, symlinks and enumeration and service control to v2 with generic READY handle exports, add the new POSIX errno/status surface and rebuild fds around posixsrv-owned shared open descriptions, descriptor-local flags, exact inheritance snapshots, dup/fcntl/record locks and shared-ring pipes, add waitpid, POSIX credentials/groups/sessions/passwd/group lookups and move sockets onto stable open descriptions, port the shells and apps to the new console/input services and surface commit API, teach init to load services.ini and start/supervise RSSRV, DWM and generic services under restricted capabilities while publishing their endpoints through RSSRV, split RSSRV into namespace, volume, display, session, input, console and resource components and make /SystemRoot a real symlink to the independently published system volume, shift the syscall table again, expand lock/resource/surface/POSIX/service/thread smoke coverage, make docs scream ASK QUESTIONS and DISCUSS at LLMs, replace MIT No Attribution with WTFPL+.
2026-09-16 10:33:20 +02:00

434 lines
19 KiB
C

// RSSRV native console. One logical console owns text output policy and consumes RSSRV logical input.
#include "rssrv_internal.h"
#include <stdlib.h>
#include <string.h>
#define RSSRV_EVENT_MANUAL_RESET 1u
#define RSSRV_CONSOLE_KEY_QUEUE_MASK (RSSRV_CONSOLE_KEY_QUEUE_SIZE - 1u)
typedef struct RSSRV_CONSOLE_SESSION {
RSSRV_CONSOLE_STATE* Console;
SYS_HANDLE Endpoint;
} RSSRV_CONSOLE_SESSION;
// Acquires the console mutation lock.
static KSTATUS RssrvConsoleLock(RSSRV_CONSOLE_STATE* console) {
if (!console || console->Mutex == SYS_HANDLE_INVALID) return NTLSTATUS_INVALID_PARAMETER;
return SysWait(console->Mutex, SYS_WAIT_FLAG_NONE);
}
// Releases the console mutation lock.
static void RssrvConsoleUnlock(RSSRV_CONSOLE_STATE* console) {
if (console && console->Mutex != SYS_HANDLE_INVALID) (void)SysReleaseMutex(console->Mutex);
}
// Refreshes the console-input availability event while the console mutex is held.
static void RssrvConsoleRefreshInputEventLocked(RSSRV_CONSOLE_STATE* console) {
if (console->KeyHead != console->KeyTail) (void)SysSignalEvent(console->KeyEvent);
else (void)SysClearEvent(console->KeyEvent);
}
// Receives one focused logical key record from Session 0.
static void RssrvConsoleRouteKey(void* context, const INPUT_KEY_RECORD* record) {
RSSRV_CONSOLE_STATE* console = (RSSRV_CONSOLE_STATE*)context;
if (!console || !record || !KSUCCESS(RssrvConsoleLock(console))) return;
uint32_t next = (console->KeyTail + 1u) & RSSRV_CONSOLE_KEY_QUEUE_MASK;
if (next != console->KeyHead) {
console->KeyQueue[console->KeyTail] = *record;
console->KeyTail = next;
RssrvConsoleRefreshInputEventLocked(console);
}
RssrvConsoleUnlock(console);
}
// Receives pointer routing for the console target. The text console does not consume pointer events yet.
static void RssrvConsoleRouteMouse(void* context, const INPUT_MOUSE_EVENT* event, int32_t x, int32_t y) {
(void)context;
(void)event;
(void)x;
(void)y;
}
// Reads or peeks one keyboard record routed specifically to this console.
static KSTATUS RssrvConsoleReadInput(RSSRV_CONSOLE_STATE* console,
int peek,
uint64_t timeoutMilliseconds,
INPUT_KEY_RECORD* recordOut) {
if (!console || !recordOut) return NTLSTATUS_INVALID_PARAMETER;
for (;;) {
KSTATUS status = RssrvConsoleLock(console);
if (!KSUCCESS(status)) return status;
if (console->KeyHead != console->KeyTail) {
*recordOut = console->KeyQueue[console->KeyHead];
if (!peek) {
console->KeyHead = (console->KeyHead + 1u) & RSSRV_CONSOLE_KEY_QUEUE_MASK;
RssrvConsoleRefreshInputEventLocked(console);
}
RssrvConsoleUnlock(console);
return NTLSTATUS_SUCCESS;
}
RssrvConsoleUnlock(console);
if (timeoutMilliseconds == 0) return NTLSTATUS_WOULD_BLOCK;
status = SysWaitSingle(console->KeyEvent, SYS_WAIT_FLAG_NONE, timeoutMilliseconds);
if (!KSUCCESS(status)) return status;
}
}
// Discards all keyboard records already routed to this console.
static KSTATUS RssrvConsoleFlushInput(RSSRV_CONSOLE_STATE* console, uint32_t* discardedOut) {
if (!console) return NTLSTATUS_INVALID_PARAMETER;
KSTATUS status = RssrvConsoleLock(console);
if (!KSUCCESS(status)) return status;
uint32_t discarded = (console->KeyTail - console->KeyHead) & RSSRV_CONSOLE_KEY_QUEUE_MASK;
console->KeyHead = console->KeyTail;
RssrvConsoleRefreshInputEventLocked(console);
RssrvConsoleUnlock(console);
if (discardedOut) *discardedOut = discarded;
return NTLSTATUS_SUCCESS;
}
// Records one successful visible console mutation while holding the console lock.
static void RssrvConsoleChangedLocked(RSSRV_CONSOLE_STATE* console) {
console->Generation++;
if (console->Generation == 0) console->Generation = 1;
}
// Writes one console chunk in serialized order.
static KSTATUS RssrvConsoleWrite(RSSRV_CONSOLE_STATE* console,
const char* data,
uint32_t length,
uint32_t* writtenOut) {
if (writtenOut) *writtenOut = 0;
if (!console || length > NTL_CONSOLE_WRITE_MAX || (length && !data))
return NTLSTATUS_INVALID_PARAMETER;
KSTATUS status = RssrvConsoleLock(console);
if (!KSUCCESS(status)) return status;
status = RssrvDisplayWriteConsole(console->Display, data, length);
if (KSUCCESS(status) && length) RssrvConsoleChangedLocked(console);
RssrvConsoleUnlock(console);
if (KSUCCESS(status) && writtenOut) *writtenOut = length;
return status;
}
// Queries dimensions from the backing display and snapshots RSSRV's console generation.
static KSTATUS RssrvConsoleQueryInfo(RSSRV_CONSOLE_STATE* console, NTL_CONSOLE_INFO* infoOut) {
if (!console || !infoOut) return NTLSTATUS_INVALID_PARAMETER;
KSTATUS status = RssrvConsoleLock(console);
if (!KSUCCESS(status)) return status;
SYS_CONSOLE_INFO consoleInfo;
SYS_DISPLAY_INFO displayInfo;
memset(&consoleInfo, 0, sizeof(consoleInfo));
memset(&displayInfo, 0, sizeof(displayInfo));
status = RssrvDisplayQueryConsoleInfo(console->Display, &consoleInfo);
if (KSUCCESS(status)) status = RssrvDisplayQueryInfo(console->Display, &displayInfo);
if (KSUCCESS(status)) {
memset(infoOut, 0, sizeof(*infoOut));
infoOut->Size = sizeof(*infoOut);
infoOut->Flags = NTL_CONSOLE_FLAG_NONE;
infoOut->Columns = consoleInfo.Columns;
infoOut->Rows = consoleInfo.Rows;
infoOut->CursorX = consoleInfo.CursorX;
infoOut->CursorY = consoleInfo.CursorY;
infoOut->Attribute = consoleInfo.Attribute;
infoOut->PixelWidth = displayInfo.Mode.Width;
infoOut->PixelHeight = displayInfo.Mode.Height;
infoOut->Generation = console->Generation;
}
RssrvConsoleUnlock(console);
return status;
}
// Sends one fixed console reply.
static KSTATUS RssrvConsoleReply(SYS_HANDLE endpoint,
uint32_t messageId,
uint32_t operation,
KSTATUS status,
uint32_t written,
const INPUT_KEY_RECORD* input,
const NTL_CONSOLE_INFO* consoleInfo) {
NTL_CONSOLE_REPLY reply;
memset(&reply, 0, sizeof(reply));
reply.Size = sizeof(reply);
reply.Version = NTL_CONSOLE_PROTOCOL_VERSION;
reply.Operation = operation;
reply.Flags = NTL_CONSOLE_FLAG_NONE;
reply.Status = status;
reply.Written = written;
if (input) reply.Input = *input;
if (consoleInfo) reply.Info = *consoleInfo;
SYS_REPLY_PORT_REQUEST request;
memset(&request, 0, sizeof(request));
request.Size = sizeof(request);
request.EndpointHandle = endpoint;
request.MessageId = messageId;
request.Flags = SYS_PORT_FLAG_NONE;
request.Payload = &reply;
request.PayloadLength = sizeof(reply);
return SysReplyPort(&request);
}
// Handles one mutation request that must be serialized through the console lock.
static KSTATUS RssrvConsoleMutate(RSSRV_CONSOLE_STATE* console,
const NTL_CONSOLE_REQUEST* request,
uint32_t* writtenOut) {
if (writtenOut) *writtenOut = 0;
KSTATUS status = RssrvConsoleLock(console);
if (!KSUCCESS(status)) return status;
size_t written = 0;
switch (request->Operation) {
case NTL_CONSOLE_OP_SET_CURSOR:
status = RssrvDisplaySetConsoleCursor(console->Display,
request->Data.Cursor.X,
request->Data.Cursor.Y);
break;
case NTL_CONSOLE_OP_FILL_CHARACTER:
if (request->Data.FillCharacter.Reserved[0] || request->Data.FillCharacter.Reserved[1] ||
request->Data.FillCharacter.Reserved[2]) {
status = NTLSTATUS_INVALID_PARAMETER;
break;
}
status = RssrvDisplayFillConsoleCharacter(console->Display,
request->Data.FillCharacter.Character,
request->Data.FillCharacter.X,
request->Data.FillCharacter.Y,
request->Data.FillCharacter.Length,
&written);
break;
case NTL_CONSOLE_OP_FILL_ATTRIBUTE:
if (request->Data.FillAttribute.Reserved != 0) {
status = NTLSTATUS_INVALID_PARAMETER;
break;
}
status = RssrvDisplayFillConsoleAttribute(console->Display,
request->Data.FillAttribute.Attribute,
request->Data.FillAttribute.X,
request->Data.FillAttribute.Y,
request->Data.FillAttribute.Length,
&written);
break;
case NTL_CONSOLE_OP_PUT_CHARACTER:
if (request->Data.PutCharacter.Reserved[0] || request->Data.PutCharacter.Reserved[1] ||
request->Data.PutCharacter.Reserved[2]) {
status = NTLSTATUS_INVALID_PARAMETER;
break;
}
status = RssrvDisplayPutConsoleCharacter(console->Display,
request->Data.PutCharacter.Character,
request->Data.PutCharacter.X,
request->Data.PutCharacter.Y,
request->Data.PutCharacter.Foreground,
request->Data.PutCharacter.Background);
break;
case NTL_CONSOLE_OP_CLEAR:
status = RssrvDisplayClearConsole(console->Display, request->Data.Clear.Color);
break;
default:
status = NTLSTATUS_INVALID_PARAMETER;
break;
}
if (KSUCCESS(status)) RssrvConsoleChangedLocked(console);
RssrvConsoleUnlock(console);
if (writtenOut) *writtenOut = written > UINT32_MAX ? UINT32_MAX : (uint32_t)written;
return status;
}
// Serves one connected logical-console client.
static void RssrvConsoleSessionThread(uintptr_t argument) {
RSSRV_CONSOLE_SESSION* session = (RSSRV_CONSOLE_SESSION*)argument;
if (!session || !session->Console) {
if (session) free(session);
SysExitThread(1);
}
RSSRV_CONSOLE_STATE* console = session->Console;
SYS_HANDLE endpoint = session->Endpoint;
free(session);
RssrvSetCurrentThreadName("rssrv.console.session");
for (;;) {
NTL_CONSOLE_REQUEST request;
memset(&request, 0, sizeof(request));
SYS_PORT_MESSAGE_INFO info;
memset(&info, 0, sizeof(info));
info.Size = sizeof(info);
SYS_RECEIVE_PORT_REQUEST receive;
memset(&receive, 0, sizeof(receive));
receive.Size = sizeof(receive);
receive.EndpointHandle = endpoint;
receive.Flags = SYS_PORT_FLAG_NONE;
receive.Payload = &request;
receive.PayloadCapacity = sizeof(request);
receive.InfoOut = &info;
receive.TimeoutMilliseconds = SYS_PORT_TIMEOUT_INFINITE;
KSTATUS status = SysReceivePort(&receive);
if (status == NTLSTATUS_PORT_DISCONNECTED) break;
if (!KSUCCESS(status)) break;
if (info.Type != SYS_PORT_MESSAGE_REQUEST || info.PayloadLength != sizeof(request) ||
info.HandleCount != 0 || request.Size != sizeof(request) ||
request.Version != NTL_CONSOLE_PROTOCOL_VERSION || request.Flags != NTL_CONSOLE_FLAG_NONE) {
(void)RssrvConsoleReply(endpoint, info.MessageId, request.Operation,
NTLSTATUS_INVALID_PARAMETER, 0, 0, 0);
continue;
}
uint32_t written = 0;
INPUT_KEY_RECORD input;
NTL_CONSOLE_INFO consoleInfo;
memset(&input, 0, sizeof(input));
memset(&consoleInfo, 0, sizeof(consoleInfo));
switch (request.Operation) {
case NTL_CONSOLE_OP_WRITE:
if (request.Data.Write.Length > NTL_CONSOLE_WRITE_MAX) status = NTLSTATUS_INVALID_PARAMETER;
else status = RssrvConsoleWrite(console, request.Data.Write.Data,
request.Data.Write.Length, &written);
(void)RssrvConsoleReply(endpoint, info.MessageId, request.Operation,
status, written, 0, 0);
break;
case NTL_CONSOLE_OP_QUERY_INFO:
status = RssrvConsoleQueryInfo(console, &consoleInfo);
(void)RssrvConsoleReply(endpoint, info.MessageId, request.Operation,
status, 0, 0, KSUCCESS(status) ? &consoleInfo : 0);
break;
case NTL_CONSOLE_OP_SET_CURSOR:
case NTL_CONSOLE_OP_FILL_CHARACTER:
case NTL_CONSOLE_OP_FILL_ATTRIBUTE:
case NTL_CONSOLE_OP_PUT_CHARACTER:
case NTL_CONSOLE_OP_CLEAR:
status = RssrvConsoleMutate(console, &request, &written);
(void)RssrvConsoleReply(endpoint, info.MessageId, request.Operation,
status, written, 0, 0);
break;
case NTL_CONSOLE_OP_READ_INPUT:
status = RssrvConsoleReadInput(console, 0,
request.TimeoutMilliseconds, &input);
(void)RssrvConsoleReply(endpoint, info.MessageId, request.Operation,
status, 0, KSUCCESS(status) ? &input : 0, 0);
break;
case NTL_CONSOLE_OP_PEEK_INPUT:
status = RssrvConsoleReadInput(console, 1,
request.TimeoutMilliseconds, &input);
(void)RssrvConsoleReply(endpoint, info.MessageId, request.Operation,
status, 0, KSUCCESS(status) ? &input : 0, 0);
break;
case NTL_CONSOLE_OP_FLUSH_INPUT:
status = RssrvConsoleFlushInput(console, &written);
(void)RssrvConsoleReply(endpoint, info.MessageId, request.Operation,
status, written, 0, 0);
break;
default:
(void)RssrvConsoleReply(endpoint, info.MessageId, request.Operation,
NTLSTATUS_INVALID_PARAMETER, 0, 0, 0);
break;
}
}
(void)SysCloseHandle(endpoint);
SysExitThread(0);
}
// Accepts console sessions and gives each connection one worker thread.
static void RssrvConsoleListenerThread(uintptr_t argument) {
RSSRV_CONSOLE_STATE* console = (RSSRV_CONSOLE_STATE*)argument;
RssrvSetCurrentThreadName("rssrv.console.listen");
if (!console) SysExitThread(1);
for (;;) {
SYS_HANDLE endpoint = SYS_HANDLE_INVALID;
SYS_ACCEPT_PORT_REQUEST accept;
memset(&accept, 0, sizeof(accept));
accept.Size = sizeof(accept);
accept.ListenerHandle = console->Listener;
accept.Flags = SYS_PORT_ACCEPT_FLAG_NONE;
accept.TimeoutMilliseconds = SYS_PORT_TIMEOUT_INFINITE;
accept.EndpointHandleOut = &endpoint;
KSTATUS status = SysAcceptPort(&accept);
if (!KSUCCESS(status)) RssrvExit(16);
RSSRV_CONSOLE_SESSION* session = (RSSRV_CONSOLE_SESSION*)malloc(sizeof(*session));
if (!session) {
(void)SysCloseHandle(endpoint);
continue;
}
session->Console = console;
session->Endpoint = endpoint;
SYS_CREATE_THREAD_REQUEST create;
memset(&create, 0, sizeof(create));
create.Size = sizeof(create);
create.Entry = (uintptr_t)RssrvConsoleSessionThread;
create.Argument = (uintptr_t)session;
create.Priority = 1;
create.Flags = SYS_CREATE_THREAD_FLAG_NONE;
KSTATUS createStatus = SysCreateThread(&create);
if (!KSUCCESS(createStatus)) {
free(session);
(void)SysCloseHandle(endpoint);
}
}
}
// Initializes Session 0's native console object and registers its focused-input target.
KSTATUS RssrvConsoleInitialize(RSSRV_CONSOLE_STATE* console,
RSSRV_NAMESPACE* namespaceState,
RSSRV_DISPLAY_STATE* display,
RSSRV_SESSION_STATE* session) {
if (!console || !namespaceState || !display || !session)
return NTLSTATUS_INVALID_PARAMETER;
memset(console, 0, sizeof(*console));
console->Mutex = SYS_HANDLE_INVALID;
console->KeyEvent = SYS_HANDLE_INVALID;
console->Listener = SYS_HANDLE_INVALID;
console->Namespace = namespaceState;
console->Display = display;
console->Session = session;
console->Generation = 1;
KSTATUS status = SysCreateMutex(&console->Mutex);
if (!KSUCCESS(status)) return status;
status = SysCreateEvent(RSSRV_EVENT_MANUAL_RESET, 0, &console->KeyEvent);
if (!KSUCCESS(status)) return status;
status = RssrvSessionRegisterInputTarget(session, &console->InputTarget,
RSSRV_INPUT_TARGET_KIND_CONSOLE, console,
RssrvConsoleRouteKey, RssrvConsoleRouteMouse);
if (!KSUCCESS(status)) return status;
return RssrvSessionActivateInputTarget(session, &console->InputTarget);
}
// Publishes /Services/Console and starts its client listener.
KSTATUS RssrvConsoleStart(RSSRV_CONSOLE_STATE* console) {
if (!console || !console->Namespace || !console->Display || !console->Session ||
console->Listener != SYS_HANDLE_INVALID)
return NTLSTATUS_INVALID_PARAMETER;
SYS_CREATE_PORT_REQUEST createPort;
memset(&createPort, 0, sizeof(createPort));
createPort.Size = sizeof(createPort);
createPort.Flags = SYS_PORT_FLAG_NONE;
createPort.Backlog = RSSRV_LISTENER_BACKLOG;
createPort.HandleOut = &console->Listener;
KSTATUS status = SysCreatePort(&createPort);
if (!KSUCCESS(status)) return status;
status = RssrvNamespaceBindOwned(console->Namespace,
NTL_CONSOLE_SERVICE_PATH,
sizeof(NTL_CONSOLE_SERVICE_PATH) - 1u,
NTL_RESOURCE_KIND_CAPABILITY,
console->Listener,
SYS_RIGHT_QUERY | SYS_PORT_RIGHT_CONNECT);
if (!KSUCCESS(status)) return status;
SYS_CREATE_THREAD_REQUEST create;
memset(&create, 0, sizeof(create));
create.Size = sizeof(create);
create.Entry = (uintptr_t)RssrvConsoleListenerThread;
create.Argument = (uintptr_t)console;
create.Priority = 1;
create.Flags = SYS_CREATE_THREAD_FLAG_NONE;
return SysCreateThread(&create);
}