2290 lines
88 KiB
C
2290 lines
88 KiB
C
// User application entry points for shell_native.
|
|
|
|
#include <stdint.h>
|
|
#include <stddef.h>
|
|
#include <stdbool.h>
|
|
#include <string.h>
|
|
#include <ntlite/syscall.h>
|
|
#include <ntlite/input_service.h>
|
|
#include <ntlite/console_service.h>
|
|
#include <ntlite/process_loader.h>
|
|
#include <ntlite/path.h>
|
|
#include <ntlite/resource.h>
|
|
#include <ntlite/status.h>
|
|
|
|
#define MAX_CMD 256
|
|
#define SHELL_FOREGROUND_POLL_MS 30u
|
|
#define MAX_PROCS 64 // Maximum number of processes we will fetch at once
|
|
#define MAX_THREADS 128
|
|
#define SHELL_PROMPT "> "
|
|
#define SHELL_GLYPH_WIDTH 8
|
|
#define SHELL_GLYPH_HEIGHT 16
|
|
#define SHELL_TEXT_FOREGROUND 0x00CCCCCCu
|
|
#define SHELL_TEXT_BACKGROUND 0x00012456u
|
|
#define SHELL_CURSOR_FOREGROUND 0x00000000u
|
|
#define SHELL_CURSOR_BACKGROUND 0x00E0E0E0u
|
|
|
|
// Output helpers.
|
|
static void print(const char* s) { (void)NtlConsoleWrite(SYS_HANDLE_INVALID, s, strlen(s), 0); }
|
|
|
|
// Opens file.
|
|
static uint32_t open_file(const char* path, uint32_t* handleOut) {
|
|
return NtlOpenFile(SysSvFromCstr(path), SYS_FILE_ACCESS_READ | SYS_FILE_ACCESS_QUERY,
|
|
SYS_FILE_OPEN_EXISTING, SYS_FILE_FLAG_NONE, handleOut);
|
|
}
|
|
|
|
// Creates file.
|
|
static uint32_t create_file(const char* path, uint32_t* handleOut) {
|
|
return NtlOpenFile(SysSvFromCstr(path), SYS_FILE_ACCESS_WRITE | SYS_FILE_ACCESS_QUERY,
|
|
SYS_FILE_CREATE_ALWAYS, SYS_FILE_FLAG_NONE, handleOut);
|
|
}
|
|
|
|
// Opens dir.
|
|
static uint32_t open_dir(const char* path, uint32_t* handleOut) {
|
|
return NtlOpenDir(SysSvFromCstr(path), SYS_DIR_ACCESS_ENUMERATE | SYS_DIR_ACCESS_OPEN,
|
|
SYS_DIR_FLAG_NONE, handleOut);
|
|
}
|
|
|
|
// Updates cwd.
|
|
static uint32_t set_cwd(const char* path) {
|
|
return NtlSetCurrentDirectory(SysSvFromCstr(path));
|
|
}
|
|
|
|
// Releases file.
|
|
static uint32_t delete_file(const char* path) {
|
|
return NtlDeleteFile(SysSvFromCstr(path));
|
|
}
|
|
|
|
// Creates process.
|
|
static uint32_t create_process(const char* path, uint32_t* handleOut, const char* cmdline) {
|
|
uint32_t imageFile = SYS_HANDLE_INVALID;
|
|
uint32_t status = open_file(path, &imageFile);
|
|
if (status != NTLSTATUS_SUCCESS) return status;
|
|
|
|
NTL_CREATE_SYSTEM_PROCESS_REQUEST req;
|
|
memset(&req, 0, sizeof(req));
|
|
req.Size = sizeof(req);
|
|
req.Flags = NTL_PROCESS_LOAD_FLAG_NONE;
|
|
req.ImageFile = imageFile;
|
|
req.Path = SysSvFromCstr(path);
|
|
req.CommandLine = SysSvFromCstr(cmdline);
|
|
req.SecurityContext = SYS_SECURITY_CONTEXT_CURRENT;
|
|
req.ResolveModuleName = NtlResolveSystemModuleName;
|
|
req.ProcessHandleOut = handleOut;
|
|
status = NtlCreateSystemProcess(&req);
|
|
(void)SysCloseHandle(imageFile);
|
|
return status;
|
|
}
|
|
|
|
// Prints u Int.
|
|
static void printUInt(uint32_t n) {
|
|
char buf[12]; int i = 0;
|
|
if (n == 0) { print("0"); return; }
|
|
while (n > 0) { buf[i++] = (char)('0' + n % 10); n /= 10; }
|
|
while (i > 0) { char c[2] = { buf[--i], '\0' }; print(c); }
|
|
}
|
|
|
|
// Prints int.
|
|
static void printInt(int32_t n) {
|
|
if (n < 0) { print("-"); printUInt((uint32_t)-(n + 1) + 1); }
|
|
else { printUInt((uint32_t)n); }
|
|
}
|
|
|
|
// Parses an unsigned decimal integer.
|
|
static bool parseUInt(const char* text, uint32_t* out) {
|
|
if (!text || !out) return false;
|
|
while (*text == ' ' || *text == '\t') text++;
|
|
if (*text < '0' || *text > '9') return false;
|
|
|
|
uint32_t value = 0;
|
|
while (*text >= '0' && *text <= '9') {
|
|
uint32_t digit = (uint32_t)(*text - '0');
|
|
if (value > (0xffffffffu - digit) / 10u) return false;
|
|
value = value * 10u + digit;
|
|
text++;
|
|
}
|
|
|
|
while (*text == ' ' || *text == '\t') text++;
|
|
if (*text != '\0') return false;
|
|
*out = value;
|
|
return true;
|
|
}
|
|
|
|
// Prints u Int64.
|
|
static void printUInt64(uint64_t n) {
|
|
if (n == 0) { print("0"); return; }
|
|
char buf[20]; int i = 0;
|
|
while (n > 0) { buf[i++] = (char)('0' + (int)(n % 10)); n /= 10; }
|
|
while (i > 0) { char c[2] = { buf[--i], '\0' }; print(c); }
|
|
}
|
|
|
|
// Implements the u64 Digits operation.
|
|
static uint32_t u64Digits(uint64_t n) {
|
|
uint32_t digits = 1;
|
|
while (n >= 10u) { n /= 10u; digits++; }
|
|
return digits;
|
|
}
|
|
|
|
// Prints spaces.
|
|
static void printSpaces(size_t count) {
|
|
while (count--) print(" ");
|
|
}
|
|
|
|
// Prints u Int Width.
|
|
static void printUIntWidth(uint32_t n, size_t width) {
|
|
uint32_t digits = u64Digits(n);
|
|
if (width > digits) printSpaces(width - digits);
|
|
printUInt(n);
|
|
}
|
|
|
|
// Prints u Int Zero Width.
|
|
static void printUIntZeroWidth(uint32_t n, size_t width) {
|
|
uint32_t digits = u64Digits(n);
|
|
while (width > digits) { print("0"); width--; }
|
|
printUInt(n);
|
|
}
|
|
|
|
// Prints u Int64 Width.
|
|
static void printUInt64Width(uint64_t n, size_t width) {
|
|
uint32_t digits = u64Digits(n);
|
|
if (width > digits) printSpaces(width - digits);
|
|
printUInt64(n);
|
|
}
|
|
|
|
// Names nt-lite process-abort exit statuses.
|
|
static const char* processExitStatusName(uint32_t code) {
|
|
switch (code) {
|
|
case NTLSTATUS_PROCESS_KILLED: return "killed";
|
|
case NTLSTATUS_PROCESS_PAGE_FAULT: return "page-fault";
|
|
case NTLSTATUS_PROCESS_GENERAL_PROTECTION: return "general-protection";
|
|
case NTLSTATUS_PROCESS_ILLEGAL_OPCODE: return "illegal-opcode";
|
|
case NTLSTATUS_PROCESS_DIVIDE_BY_ZERO: return "divide-by-zero";
|
|
case NTLSTATUS_PROCESS_BREAKPOINT: return "breakpoint";
|
|
case NTLSTATUS_PROCESS_ALIGNMENT_FAULT: return "alignment-fault";
|
|
case NTLSTATUS_PROCESS_UNKNOWN_FAULT: return "unknown-fault";
|
|
default: return NULL;
|
|
}
|
|
}
|
|
|
|
// Prints hex32.
|
|
static void printHex32(uint32_t v) {
|
|
static const char hex[] = "0123456789abcdef";
|
|
char buf[9];
|
|
for (int i = 7; i >= 0; i--) {
|
|
buf[i] = hex[v & 0xFu];
|
|
v >>= 4;
|
|
}
|
|
buf[8] = '\0';
|
|
print(buf);
|
|
}
|
|
|
|
// Prints dir Time.
|
|
static void printDirTime(const SYS_SYSTEM_TIME* t) {
|
|
if (!t || !(t->Flags & SYS_TIME_FLAG_VALID)) {
|
|
print("---- -- -- --:-- ");
|
|
return;
|
|
}
|
|
printUIntZeroWidth(t->Year, 4);
|
|
print("-");
|
|
printUIntZeroWidth(t->Month, 2);
|
|
print("-");
|
|
printUIntZeroWidth(t->Day, 2);
|
|
print(" ");
|
|
printUIntZeroWidth(t->Hour, 2);
|
|
print(":");
|
|
printUIntZeroWidth(t->Minute, 2);
|
|
print(" ");
|
|
}
|
|
|
|
// Prints string Width.
|
|
static void printStringWidth(const char* s, size_t width) {
|
|
size_t len = s ? strlen(s) : 0;
|
|
if (s) print(s);
|
|
if (width > len) printSpaces(width - len);
|
|
}
|
|
|
|
#define SHELL_HISTORY_DEPTH 16
|
|
|
|
static char gHistory[SHELL_HISTORY_DEPTH][MAX_CMD];
|
|
static size_t gHistoryCount;
|
|
|
|
typedef struct SHELL_EDITOR {
|
|
char* Buffer;
|
|
int Capacity;
|
|
int Length;
|
|
int Cursor;
|
|
} SHELL_EDITOR;
|
|
|
|
typedef struct SHELL_VISIBLE_CURSOR {
|
|
int Column;
|
|
int Row;
|
|
char Character;
|
|
bool Visible;
|
|
} SHELL_VISIBLE_CURSOR;
|
|
|
|
static const char* const gShellBuiltinCommands[] = {
|
|
"cd",
|
|
"clear",
|
|
"crash",
|
|
"date",
|
|
"del",
|
|
"dir",
|
|
"display",
|
|
"exit",
|
|
"surfbench",
|
|
"gpu",
|
|
"help",
|
|
"kill",
|
|
"mem",
|
|
"ps",
|
|
"stats",
|
|
"telemetry",
|
|
"threads",
|
|
"time",
|
|
"type",
|
|
"volume",
|
|
"write",
|
|
};
|
|
|
|
// Converts a key event into the shell's printable character model.
|
|
static char shellKeyToChar(INPUT_KEY key, uint32_t modifiers) {
|
|
bool shifted = (modifiers & INPUT_MOD_SHIFT) != 0;
|
|
bool caps = (modifiers & INPUT_MOD_CAPS_LOCK) != 0;
|
|
|
|
if (key >= INPUT_KEY_A && key <= INPUT_KEY_Z) {
|
|
char c = (char)('a' + (key - INPUT_KEY_A));
|
|
if (shifted ^ caps) c = (char)(c - 'a' + 'A');
|
|
return c;
|
|
}
|
|
|
|
if (key >= INPUT_KEY_1 && key <= INPUT_KEY_9) {
|
|
static const char shiftedDigits[] = "!@#$%^&*(";
|
|
char normal = (char)('1' + (key - INPUT_KEY_1));
|
|
return shifted ? shiftedDigits[key - INPUT_KEY_1] : normal;
|
|
}
|
|
|
|
switch (key) {
|
|
case INPUT_KEY_0: return shifted ? ')' : '0';
|
|
case INPUT_KEY_MINUS: return shifted ? '_' : '-';
|
|
case INPUT_KEY_EQUAL: return shifted ? '+' : '=';
|
|
case INPUT_KEY_LEFT_BRACKET: return shifted ? '{' : '[';
|
|
case INPUT_KEY_RIGHT_BRACKET: return shifted ? '}' : ']';
|
|
case INPUT_KEY_BACKSLASH: return shifted ? '|' : '\\';
|
|
case INPUT_KEY_SEMICOLON: return shifted ? ':' : ';';
|
|
case INPUT_KEY_APOSTROPHE: return shifted ? '"' : '\'';
|
|
case INPUT_KEY_GRAVE: return shifted ? '~' : '`';
|
|
case INPUT_KEY_COMMA: return shifted ? '<' : ',';
|
|
case INPUT_KEY_PERIOD: return shifted ? '>' : '.';
|
|
case INPUT_KEY_SLASH: return shifted ? '?' : '/';
|
|
case INPUT_KEY_SPACE: return ' ';
|
|
case INPUT_KEY_TAB: return '\t';
|
|
case INPUT_KEY_KP_DIVIDE: return '/';
|
|
case INPUT_KEY_KP_MULTIPLY: return '*';
|
|
case INPUT_KEY_KP_MINUS: return '-';
|
|
case INPUT_KEY_KP_PLUS: return '+';
|
|
case INPUT_KEY_KP_0: return '0';
|
|
case INPUT_KEY_KP_1: return '1';
|
|
case INPUT_KEY_KP_2: return '2';
|
|
case INPUT_KEY_KP_3: return '3';
|
|
case INPUT_KEY_KP_4: return '4';
|
|
case INPUT_KEY_KP_5: return '5';
|
|
case INPUT_KEY_KP_6: return '6';
|
|
case INPUT_KEY_KP_7: return '7';
|
|
case INPUT_KEY_KP_8: return '8';
|
|
case INPUT_KEY_KP_9: return '9';
|
|
case INPUT_KEY_KP_PERIOD: return '.';
|
|
default: return 0;
|
|
}
|
|
}
|
|
|
|
// Adds a non-empty command to the in-memory history ring.
|
|
static void shellHistoryAdd(const char* cmd) {
|
|
if (!cmd || !*cmd) return;
|
|
if (gHistoryCount > 0 && strcmp(gHistory[gHistoryCount - 1], cmd) == 0) return;
|
|
|
|
if (gHistoryCount == SHELL_HISTORY_DEPTH) {
|
|
memmove(gHistory[0], gHistory[1], (SHELL_HISTORY_DEPTH - 1) * MAX_CMD);
|
|
gHistoryCount--;
|
|
}
|
|
|
|
strncpy(gHistory[gHistoryCount], cmd, MAX_CMD - 1);
|
|
gHistory[gHistoryCount][MAX_CMD - 1] = '\0';
|
|
gHistoryCount++;
|
|
}
|
|
|
|
// Restores the character underneath the visible edit cursor.
|
|
static void shellEditorHideVisibleCursor(SHELL_VISIBLE_CURSOR* cursor) {
|
|
if (!cursor || !cursor->Visible) return;
|
|
|
|
(void)NtlConsolePutCharacter(SYS_HANDLE_INVALID, cursor->Character,
|
|
cursor->Column * SHELL_GLYPH_WIDTH,
|
|
cursor->Row * SHELL_GLYPH_HEIGHT,
|
|
SHELL_TEXT_FOREGROUND, SHELL_TEXT_BACKGROUND);
|
|
cursor->Visible = false;
|
|
}
|
|
|
|
// Draws the same steady block cursor used by edit without changing console state.
|
|
static void shellEditorShowVisibleCursor(SHELL_VISIBLE_CURSOR* cursor, const SHELL_EDITOR* editor) {
|
|
if (!cursor || !editor) return;
|
|
shellEditorHideVisibleCursor(cursor);
|
|
|
|
NTL_CONSOLE_INFO info;
|
|
if (NtlConsoleQueryInfo(SYS_HANDLE_INVALID, &info) != NTLSTATUS_SUCCESS) return;
|
|
|
|
char character = editor->Cursor < editor->Length
|
|
? editor->Buffer[editor->Cursor]
|
|
: ' ';
|
|
if (NtlConsolePutCharacter(SYS_HANDLE_INVALID, character,
|
|
(int)info.CursorX * SHELL_GLYPH_WIDTH,
|
|
(int)info.CursorY * SHELL_GLYPH_HEIGHT,
|
|
SHELL_CURSOR_FOREGROUND, SHELL_CURSOR_BACKGROUND) != NTLSTATUS_SUCCESS)
|
|
return;
|
|
|
|
cursor->Column = (int)info.CursorX;
|
|
cursor->Row = (int)info.CursorY;
|
|
cursor->Character = character;
|
|
cursor->Visible = true;
|
|
}
|
|
|
|
// Moves the visible cursor left by the requested number of cells.
|
|
static void shellEditorMoveLeftCells(int count) {
|
|
while (count-- > 0) print("\b");
|
|
}
|
|
|
|
// Moves the editor cursor without changing the buffer.
|
|
static void shellEditorMoveCursor(SHELL_EDITOR* editor, int target) {
|
|
if (!editor) return;
|
|
if (target < 0) target = 0;
|
|
if (target > editor->Length) target = editor->Length;
|
|
|
|
if (target < editor->Cursor) {
|
|
shellEditorMoveLeftCells(editor->Cursor - target);
|
|
} else {
|
|
while (editor->Cursor < target) {
|
|
char echo[2] = { editor->Buffer[editor->Cursor], '\0' };
|
|
print(echo);
|
|
editor->Cursor++;
|
|
}
|
|
}
|
|
editor->Cursor = target;
|
|
}
|
|
|
|
// Redraws the changed tail and restores the requested cursor position.
|
|
static void shellEditorRedrawTail(SHELL_EDITOR* editor, int start, int oldLength, int targetCursor) {
|
|
if (!editor) return;
|
|
|
|
print(editor->Buffer + start);
|
|
if (oldLength > editor->Length) printSpaces((size_t)(oldLength - editor->Length));
|
|
|
|
int displayedEnd = oldLength > editor->Length ? oldLength : editor->Length;
|
|
shellEditorMoveLeftCells(displayedEnd - targetCursor);
|
|
editor->Cursor = targetCursor;
|
|
}
|
|
|
|
// Replaces one buffer range and keeps the visible line synchronized.
|
|
static bool shellEditorReplaceRange(SHELL_EDITOR* editor, int start, int end, const char* text) {
|
|
if (!editor || !editor->Buffer || editor->Capacity <= 0 || !text) return false;
|
|
if (start < 0 || end < start || end > editor->Length) return false;
|
|
|
|
shellEditorMoveCursor(editor, start);
|
|
|
|
int oldLength = editor->Length;
|
|
int removed = end - start;
|
|
int available = editor->Capacity - 1 - (oldLength - removed);
|
|
int replacementLength = 0;
|
|
while (text[replacementLength] && replacementLength < available) replacementLength++;
|
|
|
|
memmove(editor->Buffer + start + replacementLength,
|
|
editor->Buffer + end,
|
|
(size_t)(oldLength - end + 1));
|
|
if (replacementLength > 0)
|
|
memcpy(editor->Buffer + start, text, (size_t)replacementLength);
|
|
|
|
editor->Length = oldLength - removed + replacementLength;
|
|
int targetCursor = start + replacementLength;
|
|
shellEditorRedrawTail(editor, start, oldLength, targetCursor);
|
|
return removed != replacementLength || replacementLength != 0;
|
|
}
|
|
|
|
// Inserts text at the current cursor position.
|
|
static bool shellEditorInsert(SHELL_EDITOR* editor, const char* text) {
|
|
if (!editor) return false;
|
|
return shellEditorReplaceRange(editor, editor->Cursor, editor->Cursor, text);
|
|
}
|
|
|
|
// Replaces the complete visible line and leaves the cursor at its end.
|
|
static void shellEditorReplaceLine(SHELL_EDITOR* editor, const char* text) {
|
|
if (!editor) return;
|
|
(void)shellEditorReplaceRange(editor, 0, editor->Length, text ? text : "");
|
|
}
|
|
|
|
// Returns true when a character separates shell tokens.
|
|
static bool shellIsTokenSeparator(char c) {
|
|
return c == ' ' || c == '\t';
|
|
}
|
|
|
|
// Returns true when a character separates path components.
|
|
static bool shellIsPathSeparator(char c) {
|
|
return c == '/' || c == '\\';
|
|
}
|
|
|
|
// Returns true when the token already names an explicit path.
|
|
static bool shellTokenHasPathSeparator(const char* token, size_t length) {
|
|
for (size_t i = 0; i < length; i++) {
|
|
if (shellIsPathSeparator(token[i])) return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
// Returns an ASCII lower-case value for case-insensitive shell name handling.
|
|
static char shellAsciiLower(char c) {
|
|
if (c >= 'A' && c <= 'Z')
|
|
return (char)(c - 'A' + 'a');
|
|
return c;
|
|
}
|
|
|
|
// Returns true when name starts with prefix without treating ASCII case as significant.
|
|
static bool shellNameStartsWith(const char* name, const char* prefix, size_t prefixLength) {
|
|
if (!name || !prefix) return false;
|
|
for (size_t i = 0; i < prefixLength; i++) {
|
|
if (name[i] == '\0' || shellAsciiLower(name[i]) != shellAsciiLower(prefix[i])) return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
// Extends an in-progress common prefix case-insensitively while preserving the first candidate's spelling.
|
|
static void shellUpdateCommonPrefix(char* common, size_t commonSize, const char* name, uint32_t matchCount) {
|
|
if (!common || commonSize == 0 || !name) return;
|
|
if (matchCount == 0) {
|
|
strncpy(common, name, commonSize - 1);
|
|
common[commonSize - 1] = '\0';
|
|
return;
|
|
}
|
|
|
|
size_t i = 0;
|
|
while (common[i] && name[i] && shellAsciiLower(common[i]) == shellAsciiLower(name[i])) i++;
|
|
common[i] = '\0';
|
|
}
|
|
|
|
// Prints one completion candidate.
|
|
static void shellPrintCompletionCandidate(const char* name, bool directory) {
|
|
print(" ");
|
|
print(name);
|
|
if (directory) print("/");
|
|
print("\n");
|
|
}
|
|
|
|
// Tracks completion candidates without storing the full candidate list.
|
|
typedef struct SHELL_COMPLETION_STATE {
|
|
uint32_t MatchCount;
|
|
bool LastIsDirectory;
|
|
bool LastAddsSpace;
|
|
char Common[SYS_DIR_NAME_MAX];
|
|
} SHELL_COMPLETION_STATE;
|
|
|
|
// Records one completion candidate.
|
|
static void shellRecordCompletion(SHELL_COMPLETION_STATE* state, const char* name, bool directory, bool addsSpace) {
|
|
if (!state || !name || !*name) return;
|
|
shellUpdateCommonPrefix(state->Common, sizeof(state->Common), name, state->MatchCount);
|
|
state->MatchCount++;
|
|
state->LastIsDirectory = directory;
|
|
state->LastAddsSpace = addsSpace;
|
|
}
|
|
|
|
// Collects builtin command matches for a command-position token.
|
|
static void shellCollectBuiltinCompletions(const char* prefix, size_t prefixLength,
|
|
SHELL_COMPLETION_STATE* state,
|
|
bool printMatches) {
|
|
size_t count = sizeof(gShellBuiltinCommands) / sizeof(gShellBuiltinCommands[0]);
|
|
for (size_t i = 0; i < count; i++) {
|
|
const char* name = gShellBuiltinCommands[i];
|
|
if (!shellNameStartsWith(name, prefix, prefixLength)) continue;
|
|
if (printMatches) shellPrintCompletionCandidate(name, false);
|
|
else shellRecordCompletion(state, name, false, true);
|
|
}
|
|
}
|
|
|
|
// Splits a token into a directory to scan and a filename prefix to match.
|
|
static bool shellSplitCompletionPath(const char* token, size_t tokenLength,
|
|
char* dir, size_t dirSize,
|
|
const char** prefixOut, size_t* prefixLengthOut) {
|
|
if (!token || !dir || dirSize == 0 || !prefixOut || !prefixLengthOut) return false;
|
|
|
|
size_t lastSeparator = (size_t)-1;
|
|
for (size_t i = 0; i < tokenLength; i++) {
|
|
if (shellIsPathSeparator(token[i])) lastSeparator = i;
|
|
}
|
|
|
|
if (lastSeparator == (size_t)-1) {
|
|
if (dirSize < 2) return false;
|
|
dir[0] = '.';
|
|
dir[1] = '\0';
|
|
*prefixOut = token;
|
|
*prefixLengthOut = tokenLength;
|
|
return true;
|
|
}
|
|
|
|
size_t dirLength = lastSeparator;
|
|
if (lastSeparator == 0) dirLength = 1;
|
|
if (lastSeparator >= 2 && token[1] == ':' && shellIsPathSeparator(token[2])) dirLength = lastSeparator + 1;
|
|
if (dirLength + 1 > dirSize) return false;
|
|
|
|
memcpy(dir, token, dirLength);
|
|
dir[dirLength] = '\0';
|
|
*prefixOut = token + lastSeparator + 1;
|
|
*prefixLengthOut = tokenLength - lastSeparator - 1;
|
|
return true;
|
|
}
|
|
|
|
// Collects immediate RSSRV namespace children when the completion directory is a namespace directory.
|
|
static void shellCollectResourceCompletions(const char* token, size_t tokenLength,
|
|
SHELL_COMPLETION_STATE* state,
|
|
bool printMatches) {
|
|
char dir[MAX_CMD];
|
|
const char* prefix = token;
|
|
size_t prefixLength = tokenLength;
|
|
if (!shellSplitCompletionPath(token, tokenLength, dir, sizeof(dir), &prefix, &prefixLength)) return;
|
|
if (dir[0] != '/') return;
|
|
|
|
SYS_HANDLE session = SYS_HANDLE_INVALID;
|
|
if (!KSUCCESS(NtlResourceConnect(&session))) return;
|
|
uint32_t kind = NTL_RESOURCE_KIND_INVALID;
|
|
KSTATUS status = NtlResourceQuery(session, SysSvFromCstr(dir), &kind);
|
|
if (!KSUCCESS(status) || kind != NTL_RESOURCE_KIND_NAMESPACE_DIRECTORY) {
|
|
(void)NtlResourceClose(session);
|
|
return;
|
|
}
|
|
|
|
NTL_RESOURCE_ENUM_CURSOR cursor;
|
|
memset(&cursor, 0, sizeof(cursor));
|
|
for (;;) {
|
|
NTL_RESOURCE_ENUM_ENTRY entry;
|
|
status = NtlResourceEnumerate(session, SysSvFromCstr(dir), &cursor, &entry);
|
|
if (status == NTLSTATUS_NO_MORE_FILES) break;
|
|
if (!KSUCCESS(status)) break;
|
|
if (!shellNameStartsWith(entry.Name, prefix, prefixLength)) continue;
|
|
|
|
bool directory = entry.Kind == NTL_RESOURCE_KIND_NAMESPACE_DIRECTORY ||
|
|
entry.Kind == NTL_RESOURCE_KIND_SYMBOLIC_LINK;
|
|
if (printMatches) shellPrintCompletionCandidate(entry.Name, directory);
|
|
else shellRecordCompletion(state, entry.Name, directory, true);
|
|
}
|
|
(void)NtlResourceClose(session);
|
|
}
|
|
|
|
// Collects filesystem path matches using the existing directory enumeration ABI.
|
|
static void shellCollectPathCompletions(const char* token, size_t tokenLength,
|
|
SHELL_COMPLETION_STATE* state,
|
|
bool printMatches) {
|
|
char dir[MAX_CMD];
|
|
const char* prefix = token;
|
|
size_t prefixLength = tokenLength;
|
|
|
|
if (!shellSplitCompletionPath(token, tokenLength, dir, sizeof(dir), &prefix, &prefixLength)) return;
|
|
|
|
uint32_t hDir = 0;
|
|
if (open_dir(dir, &hDir) != 0) return;
|
|
|
|
SYS_DIR_ENTRY entry;
|
|
while (SysReadDir(hDir, &entry) == 0) {
|
|
if (!entry.Name[0]) continue;
|
|
if (!shellNameStartsWith(entry.Name, prefix, prefixLength)) continue;
|
|
|
|
bool directory = (entry.Attr & 0x10u) != 0;
|
|
if (printMatches) shellPrintCompletionCandidate(entry.Name, directory);
|
|
else shellRecordCompletion(state, entry.Name, directory, true);
|
|
}
|
|
|
|
SysCloseHandle(hDir);
|
|
}
|
|
|
|
// Redraws the prompt and current edit buffer after a completion list.
|
|
static void shellRedrawPromptAndLine(SHELL_EDITOR* editor) {
|
|
print(SHELL_PROMPT);
|
|
if (!editor) return;
|
|
print(editor->Buffer);
|
|
shellEditorMoveLeftCells(editor->Length - editor->Cursor);
|
|
}
|
|
|
|
// Completes the token around the current cursor position.
|
|
static void shellCompleteLine(SHELL_EDITOR* editor) {
|
|
if (!editor || !editor->Buffer || editor->Capacity <= 0 || editor->Cursor < 0) return;
|
|
|
|
char* buf = editor->Buffer;
|
|
size_t cursor = (size_t)editor->Cursor;
|
|
size_t tokenStart = cursor;
|
|
while (tokenStart > 0 && !shellIsTokenSeparator(buf[tokenStart - 1])) tokenStart--;
|
|
size_t tokenEnd = cursor;
|
|
while (tokenEnd < (size_t)editor->Length && !shellIsTokenSeparator(buf[tokenEnd])) tokenEnd++;
|
|
|
|
const char* token = buf + tokenStart;
|
|
size_t tokenLength = cursor - tokenStart;
|
|
bool commandPosition = tokenStart == 0;
|
|
bool explicitPath = shellTokenHasPathSeparator(token, tokenLength);
|
|
|
|
char completionDir[MAX_CMD];
|
|
const char* namePrefix = token;
|
|
size_t namePrefixLength = tokenLength;
|
|
(void)shellSplitCompletionPath(token, tokenLength, completionDir, sizeof(completionDir), &namePrefix, &namePrefixLength);
|
|
|
|
SHELL_COMPLETION_STATE state;
|
|
memset(&state, 0, sizeof(state));
|
|
|
|
if (commandPosition && !explicitPath) {
|
|
shellCollectBuiltinCompletions(token, tokenLength, &state, false);
|
|
}
|
|
shellCollectResourceCompletions(token, tokenLength, &state, false);
|
|
shellCollectPathCompletions(token, tokenLength, &state, false);
|
|
|
|
if (state.MatchCount == 0) return;
|
|
|
|
size_t commonLength = strlen(state.Common);
|
|
bool expanded = commonLength > namePrefixLength;
|
|
bool canonicalized = commonLength == namePrefixLength &&
|
|
memcmp(state.Common, namePrefix, namePrefixLength) != 0;
|
|
if (expanded || canonicalized) {
|
|
size_t nameStart = tokenStart + (size_t)(namePrefix - token);
|
|
(void)shellEditorReplaceRange(editor, (int)nameStart, (int)tokenEnd, state.Common);
|
|
}
|
|
|
|
if (state.MatchCount == 1) {
|
|
if (state.LastIsDirectory) {
|
|
if (editor->Cursor < editor->Capacity - 1 && editor->Cursor > 0 &&
|
|
!shellIsPathSeparator(buf[editor->Cursor - 1]))
|
|
(void)shellEditorInsert(editor, "/");
|
|
} else if (state.LastAddsSpace) {
|
|
if (editor->Cursor < editor->Capacity - 1 &&
|
|
(editor->Cursor == editor->Length ||
|
|
!shellIsTokenSeparator(buf[editor->Cursor])))
|
|
(void)shellEditorInsert(editor, " ");
|
|
}
|
|
return;
|
|
}
|
|
|
|
if (expanded) return;
|
|
|
|
print("\n");
|
|
if (commandPosition && !explicitPath) {
|
|
shellCollectBuiltinCompletions(token, tokenLength, NULL, true);
|
|
}
|
|
shellCollectResourceCompletions(token, tokenLength, NULL, true);
|
|
shellCollectPathCompletions(token, tokenLength, NULL, true);
|
|
shellRedrawPromptAndLine(editor);
|
|
}
|
|
|
|
// Reads one editable command line from the keyboard.
|
|
static bool readLine(char* buf, int len) {
|
|
SHELL_EDITOR editor;
|
|
SHELL_VISIBLE_CURSOR visibleCursor;
|
|
char historyDraft[MAX_CMD];
|
|
size_t historyCursor = gHistoryCount;
|
|
if (len <= 0) return false;
|
|
|
|
editor.Buffer = buf;
|
|
editor.Capacity = len;
|
|
editor.Length = 0;
|
|
editor.Cursor = 0;
|
|
memset(&visibleCursor, 0, sizeof(visibleCursor));
|
|
buf[0] = '\0';
|
|
historyDraft[0] = '\0';
|
|
|
|
while (true) {
|
|
shellEditorShowVisibleCursor(&visibleCursor, &editor);
|
|
INPUT_KEY_RECORD input;
|
|
KSTATUS inputStatus = NtlInputReadEvent(SYS_HANDLE_INVALID, SYS_WAIT_TIMEOUT_INFINITE, &input);
|
|
shellEditorHideVisibleCursor(&visibleCursor);
|
|
if (inputStatus != NTLSTATUS_SUCCESS) { SysYield(); continue; }
|
|
|
|
INPUT_KEY key = input.Key;
|
|
if (key == INPUT_KEY_NONE) { SysYield(); continue; }
|
|
|
|
uint32_t modifiers = input.Modifiers;
|
|
if ((modifiers & INPUT_MOD_CTRL) && key == INPUT_KEY_C) {
|
|
buf[0] = '\0';
|
|
print("^C\n");
|
|
return true;
|
|
}
|
|
|
|
if (key == INPUT_KEY_ENTER || key == INPUT_KEY_KP_ENTER) {
|
|
print("\n");
|
|
break;
|
|
}
|
|
|
|
if (key == INPUT_KEY_BACKSPACE) {
|
|
if (editor.Cursor > 0)
|
|
(void)shellEditorReplaceRange(&editor, editor.Cursor - 1, editor.Cursor, "");
|
|
continue;
|
|
}
|
|
|
|
if (key == INPUT_KEY_DELETE) {
|
|
if (editor.Cursor < editor.Length)
|
|
(void)shellEditorReplaceRange(&editor, editor.Cursor, editor.Cursor + 1, "");
|
|
continue;
|
|
}
|
|
|
|
if (key == INPUT_KEY_LEFT) {
|
|
shellEditorMoveCursor(&editor, editor.Cursor - 1);
|
|
continue;
|
|
}
|
|
|
|
if (key == INPUT_KEY_RIGHT) {
|
|
shellEditorMoveCursor(&editor, editor.Cursor + 1);
|
|
continue;
|
|
}
|
|
|
|
if (key == INPUT_KEY_HOME) {
|
|
shellEditorMoveCursor(&editor, 0);
|
|
continue;
|
|
}
|
|
|
|
if (key == INPUT_KEY_END) {
|
|
shellEditorMoveCursor(&editor, editor.Length);
|
|
continue;
|
|
}
|
|
|
|
if (key == INPUT_KEY_UP) {
|
|
if (historyCursor > 0) {
|
|
if (historyCursor == gHistoryCount) {
|
|
strncpy(historyDraft, editor.Buffer, sizeof(historyDraft) - 1);
|
|
historyDraft[sizeof(historyDraft) - 1] = '\0';
|
|
}
|
|
historyCursor--;
|
|
shellEditorReplaceLine(&editor, gHistory[historyCursor]);
|
|
}
|
|
continue;
|
|
}
|
|
|
|
if (key == INPUT_KEY_DOWN) {
|
|
if (historyCursor < gHistoryCount) {
|
|
historyCursor++;
|
|
shellEditorReplaceLine(&editor,
|
|
historyCursor < gHistoryCount ? gHistory[historyCursor] : historyDraft);
|
|
}
|
|
continue;
|
|
}
|
|
|
|
if (key == INPUT_KEY_TAB) {
|
|
shellCompleteLine(&editor);
|
|
continue;
|
|
}
|
|
|
|
char c = input.Char ? input.Char : shellKeyToChar(key, modifiers);
|
|
if (c < 0x20 || c == 127) continue;
|
|
char text[2] = { c, '\0' };
|
|
(void)shellEditorInsert(&editor, text);
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
// Implements the trim operation.
|
|
static char* trim(char* s) {
|
|
while (*s == ' ') s++;
|
|
int len = (int)strlen(s);
|
|
while (len > 0 && s[len - 1] == ' ') len--;
|
|
s[len] = '\0';
|
|
return s;
|
|
}
|
|
|
|
// Built-in commands.
|
|
static void cmd_help() {
|
|
print("Available commands:\n"
|
|
" help - Show this list\n"
|
|
" mem - Show memory usage\n"
|
|
" time - Show system wall-clock time\n"
|
|
" display - Show display provider and counters\n"
|
|
" surfbench - Benchmark Doom-size and full-screen surface commits\n"
|
|
" stats - Show scheduler/CPU/MM telemetry\n"
|
|
" ps - List processes\n"
|
|
" threads <pid> - Show one process's thread statuses\n"
|
|
" kill <pid> - Terminate a process\n"
|
|
" dir [path] - List directory contents\n"
|
|
" cd <path> - Change directory\n"
|
|
" type <file> - Display file contents\n"
|
|
" volume [0-100] - Query or set master audio volume\n"
|
|
" volume mute - Mute the default audio endpoint\n"
|
|
" volume unmute - Unmute the default audio endpoint\n"
|
|
" del <file> - Delete a file\n"
|
|
" write <file> <tx> - Create a file with text\n"
|
|
" clear - Clear the screen\n"
|
|
" exit - Close the shell\n");
|
|
}
|
|
|
|
// Prints size Ki B.
|
|
static void printSizeKiB(uint64_t bytes) {
|
|
printUInt64(bytes / 1024u);
|
|
print(" KiB");
|
|
}
|
|
|
|
// Prints pages Ki B.
|
|
static void printPagesKiB(uint64_t pages) {
|
|
printUInt64(pages * 4u);
|
|
print(" KiB");
|
|
}
|
|
|
|
// Prints cpu Flags.
|
|
static void printCpuFlags(uint32_t flags) {
|
|
if (flags & SYS_CPU_FLAG_BOOT_PROCESSOR) print("boot ");
|
|
if (flags & SYS_CPU_FLAG_ONLINE) print("online ");
|
|
if (flags & SYS_CPU_FLAG_PARKED) print("parked ");
|
|
if (flags & SYS_CPU_FLAG_PRIMARY) print("primary ");
|
|
if (flags & SYS_CPU_FLAG_STARTED) print("started ");
|
|
if (flags & SYS_CPU_FLAG_PRESENT) print("present ");
|
|
}
|
|
|
|
// Selects the available default audio endpoint, or the first available endpoint.
|
|
static bool shellQueryDefaultAudioEndpoint(SYS_AUDIO_ENDPOINT_INFO* endpointOut) {
|
|
if (!endpointOut) return false;
|
|
|
|
SYS_AUDIO_ENDPOINT_INFO endpoints[SYS_AUDIO_MAX_ENDPOINTS];
|
|
uint32_t count = 0;
|
|
SYS_AUDIO_ENUMERATE_ENDPOINTS_REQUEST request;
|
|
memset(&request, 0, sizeof(request));
|
|
request.Size = sizeof(request);
|
|
request.Endpoints = endpoints;
|
|
request.Capacity = SYS_AUDIO_MAX_ENDPOINTS;
|
|
request.CountOut = &count;
|
|
|
|
KSTATUS status = SysAudioEnumerateEndpoints(&request);
|
|
if (status != NTLSTATUS_SUCCESS && status != NTLSTATUS_BUFFER_TOO_SMALL) {
|
|
print("volume: endpoint query failed status=");
|
|
printUInt(status);
|
|
print("\n");
|
|
return false;
|
|
}
|
|
|
|
const SYS_AUDIO_ENDPOINT_INFO* fallback = NULL;
|
|
uint32_t availableCount = count < SYS_AUDIO_MAX_ENDPOINTS
|
|
? count
|
|
: SYS_AUDIO_MAX_ENDPOINTS;
|
|
for (uint32_t index = 0; index < availableCount; index++) {
|
|
const SYS_AUDIO_ENDPOINT_INFO* endpoint = &endpoints[index];
|
|
if (endpoint->Size != sizeof(*endpoint) ||
|
|
endpoint->Version != SYS_AUDIO_ABI_VERSION ||
|
|
endpoint->State != SysAudioEndpointReady ||
|
|
(endpoint->Flags & SYS_AUDIO_ENDPOINT_FLAG_AVAILABLE) == 0)
|
|
continue;
|
|
if (!fallback) fallback = endpoint;
|
|
if (endpoint->Flags & SYS_AUDIO_ENDPOINT_FLAG_DEFAULT) {
|
|
*endpointOut = *endpoint;
|
|
return true;
|
|
}
|
|
}
|
|
|
|
if (fallback) {
|
|
*endpointOut = *fallback;
|
|
return true;
|
|
}
|
|
|
|
print("volume: no available audio endpoint\n");
|
|
return false;
|
|
}
|
|
|
|
// Converts one public Q16 gain into a rounded percentage.
|
|
static uint32_t shellAudioVolumePercent(uint32_t volumeQ16) {
|
|
uint64_t scaled = (uint64_t)volumeQ16 * 100u + SYS_AUDIO_GAIN_Q16_UNITY / 2u;
|
|
return (uint32_t)(scaled / SYS_AUDIO_GAIN_Q16_UNITY);
|
|
}
|
|
|
|
// Converts one percentage into public Q16 gain.
|
|
static uint32_t shellAudioVolumeQ16(uint32_t percent) {
|
|
return (uint32_t)(((uint64_t)percent * SYS_AUDIO_GAIN_Q16_UNITY + 50u) / 100u);
|
|
}
|
|
|
|
// Queries or updates the default endpoint master volume.
|
|
static void cmd_volume(const char* args) {
|
|
SYS_AUDIO_ENDPOINT_INFO endpoint;
|
|
if (!shellQueryDefaultAudioEndpoint(&endpoint)) return;
|
|
|
|
while (args && (*args == ' ' || *args == '\t')) args++;
|
|
if (!args || *args == '\0') {
|
|
print("Volume: ");
|
|
printUInt(shellAudioVolumePercent(endpoint.MasterVolumeQ16));
|
|
print("%");
|
|
if (endpoint.MasterMuted) print(" (muted)");
|
|
print("\n");
|
|
return;
|
|
}
|
|
|
|
uint32_t volumeQ16 = endpoint.MasterVolumeQ16;
|
|
uint32_t muted = endpoint.MasterMuted ? 1u : 0u;
|
|
if (strcmp(args, "mute") == 0) {
|
|
muted = 1u;
|
|
} else if (strcmp(args, "unmute") == 0) {
|
|
muted = 0u;
|
|
} else {
|
|
uint32_t percent = 0;
|
|
if (!parseUInt(args, &percent) || percent > 100u) {
|
|
print("Usage: volume [0-100|mute|unmute]\n");
|
|
return;
|
|
}
|
|
volumeQ16 = shellAudioVolumeQ16(percent);
|
|
muted = 0u;
|
|
}
|
|
|
|
SYS_AUDIO_SET_MASTER_VOLUME_REQUEST request;
|
|
memset(&request, 0, sizeof(request));
|
|
request.Size = sizeof(request);
|
|
request.EndpointId = endpoint.EndpointId;
|
|
request.Muted = muted;
|
|
request.VolumeQ16 = volumeQ16;
|
|
KSTATUS status = SysAudioSetMasterVolume(&request);
|
|
if (status != NTLSTATUS_SUCCESS) {
|
|
print("volume: update failed status=");
|
|
printUInt(status);
|
|
print("\n");
|
|
return;
|
|
}
|
|
|
|
print("Volume: ");
|
|
printUInt(shellAudioVolumePercent(volumeQ16));
|
|
print("%");
|
|
if (muted) print(" (muted)");
|
|
print("\n");
|
|
}
|
|
|
|
// Implements the cmd stats operation.
|
|
static void cmd_stats(void) {
|
|
SYS_SCHEDULER_STATS sched;
|
|
SYS_PMM_STATS pmm;
|
|
SYS_CPU_STATS* cpu = 0;
|
|
void* cpuBuffer = 0;
|
|
SYS_PFN_STATS pfn;
|
|
SYS_VAD_STATS vad;
|
|
SYS_SWAP_STATS swap;
|
|
SYS_FILE_CACHE_STATS fileCache;
|
|
SYS_FILESYSTEM_STATS filesystem;
|
|
SYS_DISK_STATS disk;
|
|
SYS_PTE_STATS pte;
|
|
SYS_SURFACE_STATS surface;
|
|
SYS_EXCEPTION_STATS exceptionStats;
|
|
uint32_t status;
|
|
|
|
status = SysQueryKernelStats(SYS_KERNEL_STATS_SCHEDULER, &sched, sizeof(sched), NULL);
|
|
if (status != 0) {
|
|
print("stats: scheduler query failed: ");
|
|
printUInt(status);
|
|
print("\n");
|
|
return;
|
|
}
|
|
|
|
status = SysQueryKernelStats(SYS_KERNEL_STATS_PMM, &pmm, sizeof(pmm), NULL);
|
|
if (status != 0) {
|
|
print("stats: pmm query failed: ");
|
|
printUInt(status);
|
|
print("\n");
|
|
return;
|
|
}
|
|
|
|
size_t cpuSize = 0;
|
|
status = SysQueryKernelStats(SYS_KERNEL_STATS_CPU, 0, 0, &cpuSize);
|
|
if (status != 0 || cpuSize < sizeof(SYS_CPU_STATS)) {
|
|
print("stats: cpu size query failed: ");
|
|
printUInt(status);
|
|
print("\n");
|
|
return;
|
|
}
|
|
|
|
size_t cpuPages = (cpuSize + 4095u) / 4096u;
|
|
if (cpuPages == 0) cpuPages = 1;
|
|
SYS_ALLOC_VM_REQUEST allocReq;
|
|
memset(&allocReq, 0, sizeof(allocReq));
|
|
allocReq.Size = sizeof(allocReq);
|
|
allocReq.PageCount = cpuPages;
|
|
allocReq.Protect = SYS_VM_READWRITE;
|
|
allocReq.Flags = SYS_VM_FLAG_NONE;
|
|
allocReq.BaseOut = &cpuBuffer;
|
|
status = SysAllocVm(&allocReq);
|
|
if (status != 0 || !cpuBuffer) {
|
|
print("stats: cpu buffer allocation failed: ");
|
|
printUInt(status);
|
|
print("\n");
|
|
return;
|
|
}
|
|
|
|
cpu = (SYS_CPU_STATS*)cpuBuffer;
|
|
status = SysQueryKernelStats(SYS_KERNEL_STATS_CPU, cpu, cpuPages * 4096u, NULL);
|
|
if (status != 0) {
|
|
print("stats: cpu query failed: ");
|
|
printUInt(status);
|
|
print("\n");
|
|
SysFreeVm(cpuBuffer);
|
|
return;
|
|
}
|
|
|
|
status = SysQueryKernelStats(SYS_KERNEL_STATS_PFN, &pfn, sizeof(pfn), NULL);
|
|
if (status != 0) {
|
|
print("stats: pfn query failed: ");
|
|
printUInt(status);
|
|
print("\n");
|
|
SysFreeVm(cpuBuffer);
|
|
return;
|
|
}
|
|
|
|
status = SysQueryKernelStats(SYS_KERNEL_STATS_VAD, &vad, sizeof(vad), NULL);
|
|
if (status != 0) {
|
|
print("stats: vad query failed: ");
|
|
printUInt(status);
|
|
print("\n");
|
|
SysFreeVm(cpuBuffer);
|
|
return;
|
|
}
|
|
|
|
status = SysQueryKernelStats(SYS_KERNEL_STATS_SWAP, &swap, sizeof(swap), NULL);
|
|
if (status != 0) {
|
|
print("stats: swap query failed: ");
|
|
printUInt(status);
|
|
print("\n");
|
|
SysFreeVm(cpuBuffer);
|
|
return;
|
|
}
|
|
|
|
status = SysQueryKernelStats(SYS_KERNEL_STATS_FILE_CACHE, &fileCache, sizeof(fileCache), NULL);
|
|
if (status != 0) {
|
|
print("stats: file cache query failed: ");
|
|
printUInt(status);
|
|
print("\n");
|
|
SysFreeVm(cpuBuffer);
|
|
return;
|
|
}
|
|
|
|
status = SysQueryKernelStats(SYS_KERNEL_STATS_FILESYSTEM, &filesystem, sizeof(filesystem), NULL);
|
|
if (status != 0) {
|
|
print("stats: filesystem query failed: ");
|
|
printUInt(status);
|
|
print("\n");
|
|
SysFreeVm(cpuBuffer);
|
|
return;
|
|
}
|
|
|
|
status = SysQueryKernelStats(SYS_KERNEL_STATS_DISK, &disk, sizeof(disk), NULL);
|
|
if (status != 0) {
|
|
print("stats: disk query failed: ");
|
|
printUInt(status);
|
|
print("\n");
|
|
SysFreeVm(cpuBuffer);
|
|
return;
|
|
}
|
|
|
|
status = SysQueryKernelStats(SYS_KERNEL_STATS_PTE, &pte, sizeof(pte), NULL);
|
|
if (status != 0) {
|
|
print("stats: pte query failed: ");
|
|
printUInt(status);
|
|
print("\n");
|
|
SysFreeVm(cpuBuffer);
|
|
return;
|
|
}
|
|
|
|
status = SysQueryKernelStats(SYS_KERNEL_STATS_SURFACE, &surface, sizeof(surface), NULL);
|
|
if (status != 0) {
|
|
print("stats: surface query failed: ");
|
|
printUInt(status);
|
|
print("\n");
|
|
SysFreeVm(cpuBuffer);
|
|
return;
|
|
}
|
|
|
|
status = SysQueryKernelStats(SYS_KERNEL_STATS_EXCEPTION, &exceptionStats, sizeof(exceptionStats), NULL);
|
|
if (status != 0) {
|
|
print("stats: exception query failed: ");
|
|
printUInt(status);
|
|
print("\n");
|
|
SysFreeVm(cpuBuffer);
|
|
return;
|
|
}
|
|
|
|
print("Scheduler:\n");
|
|
print(" ticks: "); printUInt64(sched.Ticks); print("\n");
|
|
print(" context switches: "); printUInt64(sched.ContextSwitches); print("\n");
|
|
print(" quantum expires: "); printUInt64(sched.QuantumExpirations); print("\n");
|
|
print(" priority preempts: "); printUInt64(sched.PriorityPreemptions); print("\n");
|
|
print(" idle selections: "); printUInt64(sched.IdleSelections); print("\n");
|
|
print(" keep-current: "); printUInt64(sched.KeepCurrentDecisions); print("\n");
|
|
print(" demotions: "); printUInt64(sched.Demotions); print("\n");
|
|
print(" aging promotions: "); printUInt64(sched.AgingPromotions); print("\n");
|
|
print(" voluntary blocks: "); printUInt64(sched.VoluntaryBlocks); print("\n");
|
|
print(" SMT deferrals: "); printUInt64(sched.SmtSiblingDeferrals); print("\n");
|
|
print(" SMT primary pokes: "); printUInt64(sched.SmtPrimaryPokes); print("\n");
|
|
print(" RT dispatches: "); printUInt64(sched.RealtimeDispatches); print("\n");
|
|
print(" RT preemptions: "); printUInt64(sched.RealtimePreemptions); print("\n");
|
|
print(" PI boosts: "); printUInt64(sched.PriorityInheritanceBoosts); print("\n");
|
|
print(" PI drops: "); printUInt64(sched.PriorityInheritanceDrops); print("\n");
|
|
print(" PI propagations: "); printUInt64(sched.PriorityInheritancePropagations); print("\n");
|
|
print(" PI max depth: "); printUInt64(sched.PriorityInheritanceMaxDepth); print("\n");
|
|
|
|
print("Exceptions:\n");
|
|
print(" delivered: "); printUInt64(exceptionStats.Delivered); print("\n");
|
|
print(" user-raised: "); printUInt64(exceptionStats.UserRaised); print("\n");
|
|
print(" continued: "); printUInt64(exceptionStats.Continued); print("\n");
|
|
print(" unhandled: "); printUInt64(exceptionStats.Unhandled); print("\n");
|
|
print(" invalid continue: "); printUInt64(exceptionStats.InvalidContinue); print("\n");
|
|
print(" max nesting: "); printUInt(exceptionStats.MaxNestedDispatch); print("\n");
|
|
print(" last code: "); printUInt(exceptionStats.LastCode); print("\n");
|
|
|
|
print("CPUs:\n");
|
|
const SYS_CPU_STAT* cpuRecords = SYS_CPU_STATS_CONST_RECORDS(cpu);
|
|
uint32_t n = cpu->RecordCount;
|
|
for (uint32_t i = 0; i < n; i++) {
|
|
const SYS_CPU_STAT* c = &cpuRecords[i];
|
|
print(" cpu"); printUInt(c->CpuIndex);
|
|
print(" hardware="); printUInt(c->HardwareId);
|
|
print(" current=");
|
|
if (c->CurrentThreadFlags & SYS_CPU_THREAD_FLAG_IDLE) {
|
|
print("idle/"); printUInt(c->CpuIndex);
|
|
} else {
|
|
print("tid/"); printUInt(c->CurrentTid);
|
|
}
|
|
print(" ticks="); printUInt64(c->SchedulerTicks);
|
|
print(" switches="); printUInt64(c->ContextSwitches);
|
|
print(" flags="); printCpuFlags(c->Flags);
|
|
print("\n");
|
|
}
|
|
|
|
print("Physical memory:\n");
|
|
print(" total: "); printPagesKiB(pmm.TotalFrames); print(" (frames="); printUInt64(pmm.TotalFrames); print(")\n");
|
|
print(" used: "); printPagesKiB(pmm.UsedFrames); print(" (frames="); printUInt64(pmm.UsedFrames); print(")\n");
|
|
print(" free: "); printPagesKiB(pmm.FreeFrames); print(" (frames="); printUInt64(pmm.FreeFrames); print(")\n");
|
|
print(" largest free run: "); printPagesKiB(pmm.LargestFreeRun); print(" (frames="); printUInt64(pmm.LargestFreeRun); print(")\n");
|
|
print(" free runs: "); printUInt64(pmm.FreeRunCount); print("\n");
|
|
print(" available+reclaim: "); printSizeKiB(pfn.AvailableFrames * 4096ull);
|
|
print(" (reclaimable="); printSizeKiB(pfn.ReclaimableFrames * 4096ull); print(")\n");
|
|
print(" run histogram:\n");
|
|
print(" 1: "); printUInt64(pmm.FreeRunHistogram[0]); print("\n");
|
|
print(" 2-3: "); printUInt64(pmm.FreeRunHistogram[1]); print("\n");
|
|
print(" 4-7: "); printUInt64(pmm.FreeRunHistogram[2]); print("\n");
|
|
print(" 8-15: "); printUInt64(pmm.FreeRunHistogram[3]); print("\n");
|
|
print(" 16-63: "); printUInt64(pmm.FreeRunHistogram[4]); print("\n");
|
|
print(" 64-255: "); printUInt64(pmm.FreeRunHistogram[5]); print("\n");
|
|
print(" 256-1023: "); printUInt64(pmm.FreeRunHistogram[6]); print("\n");
|
|
print(" 1024+: "); printUInt64(pmm.FreeRunHistogram[7]); print("\n");
|
|
|
|
print("PFN database:\n");
|
|
print(" states: active="); printUInt64(pfn.ActiveFrames);
|
|
print(" free="); printUInt64(pfn.FreeFrames);
|
|
print(" zeroed="); printUInt64(pfn.ZeroedFrames);
|
|
print(" standby="); printUInt64(pfn.StandbyFrames);
|
|
print(" modified="); printUInt64(pfn.ModifiedFrames);
|
|
print(" reserved="); printUInt64(pfn.ReservedFrames); print("\n");
|
|
print(" eviction: evictable="); printUInt64(pfn.EvictableFrames);
|
|
print(" reclaimable="); printUInt64(pfn.ReclaimableFrames);
|
|
print(" available="); printUInt64(pfn.AvailableFrames);
|
|
print(" mapped="); printUInt64(pfn.MappedFrames);
|
|
print(" non-evictable="); printUInt64(pfn.NonEvictableFrames);
|
|
print(" pinned="); printUInt64(pfn.PinnedFrames); print("\n");
|
|
print(" usage frames: kernel="); printUInt64(pfn.KernelFrames);
|
|
print(" pt="); printUInt64(pfn.PageTableFrames);
|
|
print(" user="); printUInt64(pfn.UserFrames);
|
|
print(" driver="); printUInt64(pfn.DriverFrames);
|
|
print(" dma="); printUInt64(pfn.DmaFrames);
|
|
print(" boot="); printUInt64(pfn.BootloaderFrames);
|
|
print(" pfn-db="); printUInt64(pfn.PfnDatabaseFrames);
|
|
print(" mmio="); printUInt64(pfn.MmioFrames); print("\n");
|
|
print(" buckets: clean-file="); printUInt64(pfn.CleanFileCacheFrames);
|
|
print(" (active="); printUInt64(pfn.ActiveCleanFileCacheFrames);
|
|
print(" standby="); printUInt64(pfn.StandbyCleanFileCacheFrames); print(")");
|
|
print(" image="); printUInt64(pfn.ImageFrames);
|
|
print(" (active="); printUInt64(pfn.ActiveImageFrames);
|
|
print(" standby="); printUInt64(pfn.StandbyImageFrames); print(")");
|
|
print(" mapped-file="); printUInt64(pfn.MappedFileFrames);
|
|
print(" anon="); printUInt64(pfn.AnonymousFrames);
|
|
print(" cow="); printUInt64(pfn.CowFrames);
|
|
print(" surface="); printUInt64(pfn.SurfaceFrames); print("\n");
|
|
print(" operations: alloc="); printUInt64(pfn.AllocationCount);
|
|
print(" free="); printUInt64(pfn.FreeCount);
|
|
print(" retain="); printUInt64(pfn.RetainCount);
|
|
print(" release="); printUInt64(pfn.ReleaseCount);
|
|
print(" pin="); printUInt64(pfn.PinCount);
|
|
print(" unpin="); printUInt64(pfn.UnpinCount); print("\n");
|
|
print(" failures: alloc="); printUInt64(pfn.FailedAllocations);
|
|
print(" contiguous="); printUInt64(pfn.FailedContiguousAllocations); print("\n");
|
|
print(" alloc reasons: invalid-range="); printUInt64(pfn.FailedInvalidRange);
|
|
print(" empty-range="); printUInt64(pfn.FailedEmptyRange);
|
|
print(" reserve-denied="); printUInt64(pfn.FailedReserveDenied);
|
|
print(" no-frame="); printUInt64(pfn.FailedNoFrame); print("\n");
|
|
print(" contig reasons: invalid="); printUInt64(pfn.FailedContiguousInvalidRequest);
|
|
print(" reserve-denied="); printUInt64(pfn.FailedContiguousReserveDenied);
|
|
print(" no-run="); printUInt64(pfn.FailedContiguousNoRun); print("\n");
|
|
print(" reclaim: attempts="); printUInt64(pfn.ReclaimAttempts);
|
|
print(" requested="); printUInt64(pfn.ReclaimPagesRequested);
|
|
print(" succeeded="); printUInt64(pfn.ReclaimPagesSucceeded);
|
|
print(" failed="); printUInt64(pfn.ReclaimPagesFailed);
|
|
print(" reentrant-skips="); printUInt64(pfn.ReclaimReentrantSkips); print("\n");
|
|
|
|
print("VAD faults:\n");
|
|
print(" resolve: calls="); printUInt64(vad.ResolveFaultCalls);
|
|
print(" resolved="); printUInt64(vad.ResolvedFaults);
|
|
print(" failed="); printUInt64(vad.FailedFaults); print("\n");
|
|
print(" failure reasons: protection="); printUInt64(vad.ProtectionFaults);
|
|
print(" no-vad="); printUInt64(vad.NoVadFaults);
|
|
print(" uncommitted="); printUInt64(vad.UncommittedFaults);
|
|
print(" noaccess="); printUInt64(vad.NoAccessFaults); print("\n");
|
|
print(" materialization: demand-zero="); printUInt64(vad.DemandZeroPages);
|
|
print(" zero-maps="); printUInt64(vad.ZeroPageMaps);
|
|
print(" zero-splits="); printUInt64(vad.ZeroPageCowSplits);
|
|
print(" ensure-calls="); printUInt64(vad.EnsureResidentCalls);
|
|
print(" ensure-pages="); printUInt64(vad.EnsureResidentPages);
|
|
print(" already-resident="); printUInt64(vad.AlreadyResidentPages); print("\n");
|
|
print(" cow: faults="); printUInt64(vad.CowFaults);
|
|
print(" copied="); printUInt64(vad.CowPagesCopied);
|
|
print(" views="); printUInt64(vad.CowViewsCreated);
|
|
print(" shared-pages="); printUInt64(vad.CowPagesShared); print("\n");
|
|
print(" file-backed: faults="); printUInt64(vad.FileBackedFaults);
|
|
print(" reads="); printUInt64(vad.FileBackedReads);
|
|
print(" read-fail="); printUInt64(vad.FileBackedReadFailures);
|
|
print(" discards="); printUInt64(vad.FileBackedDiscards); print("\n");
|
|
print(" mapped-file: shared-faults="); printUInt64(vad.FileBackedSharedWritableFaults);
|
|
print(" dirty-faults="); printUInt64(vad.FileBackedDirtyFaults);
|
|
print(" dirty-syncs="); printUInt64(vad.FileBackedDirtySyncs); print("\n");
|
|
print(" image-backed: faults="); printUInt64(vad.ImageBackedFaults);
|
|
print(" zero-faults="); printUInt64(vad.ImageBackedZeroFaults);
|
|
print(" private-faults="); printUInt64(vad.ImageBackedPrivateFaults); print("\n");
|
|
print(" image fixups: calls="); printUInt64(vad.ImageFixupPrivateCalls);
|
|
print(" private-pages="); printUInt64(vad.ImageFixupPrivatePages); print("\n");
|
|
print(" swap-backed: page-outs="); printUInt64(vad.SwapPageOuts);
|
|
print(" page-ins="); printUInt64(vad.SwapPageIns); print("\n");
|
|
|
|
print("Swap:\n");
|
|
print(" enabled: "); printUInt64(swap.Enabled); print("\n");
|
|
print(" slots: total="); printUInt64(swap.TotalSlots);
|
|
print(" used="); printUInt64(swap.UsedSlots);
|
|
print(" free="); printUInt64(swap.FreeSlots);
|
|
print(" high-water="); printUInt64(swap.HighWaterSlots); print("\n");
|
|
print(" io: out="); printUInt64(swap.PageOuts);
|
|
print(" in="); printUInt64(swap.PageIns);
|
|
print(" out-fail="); printUInt64(swap.PageOutFailures);
|
|
print(" in-fail="); printUInt64(swap.PageInFailures); print("\n");
|
|
print(" slots ops: alloc="); printUInt64(swap.SlotAllocations);
|
|
print(" free="); printUInt64(swap.SlotFrees);
|
|
print(" alloc-fail="); printUInt64(swap.SlotAllocationFailures); print("\n");
|
|
|
|
print("File cache:\n");
|
|
print(" lookups: total="); printUInt64(fileCache.Lookups);
|
|
print(" hits="); printUInt64(fileCache.Hits);
|
|
print(" misses="); printUInt64(fileCache.Misses); print("\n");
|
|
print(" loads: ok="); printUInt64(fileCache.Loads);
|
|
print(" fail="); printUInt64(fileCache.LoadFailures);
|
|
print(" races="); printUInt64(fileCache.RaceDiscards);
|
|
print(" waits="); printUInt64(fileCache.InflightWaits); print("\n");
|
|
print(" lifetime: resident="); printUInt64(fileCache.ResidentPages);
|
|
print(" active="); printUInt64(fileCache.ActivePages);
|
|
print(" standby="); printUInt64(fileCache.StandbyPages);
|
|
print(" max="); printUInt64(fileCache.MaxResidentPages);
|
|
print(" invalidations="); printUInt64(fileCache.Invalidations); print("\n");
|
|
print(" dirty/writeback: dirty="); printUInt64(fileCache.DirtyPages);
|
|
print(" max-dirty="); printUInt64(fileCache.MaxDirtyPages);
|
|
print(" marks="); printUInt64(fileCache.DirtyMarks);
|
|
print(" wb-req="); printUInt64(fileCache.WritebackRequests);
|
|
print(" wb-pages="); printUInt64(fileCache.WritebackPages);
|
|
print(" wb-bytes="); printUInt64(fileCache.WritebackBytes);
|
|
print(" wb-fail="); printUInt64(fileCache.WritebackFailures); print("\n");
|
|
print(" mapped dirty: syncs="); printUInt64(fileCache.MappedDirtySyncs);
|
|
print(" pages="); printUInt64(fileCache.MappedDirtyPages); print("\n");
|
|
print(" flushes: calls="); printUInt64(fileCache.FlushFileCalls);
|
|
print(" fail="); printUInt64(fileCache.FlushFileFailures); print("\n");
|
|
print(" reclaim: drops="); printUInt64(fileCache.Drops);
|
|
print(" standby="); printUInt64(fileCache.StandbyTransitions);
|
|
print(" standby-hits="); printUInt64(fileCache.StandbyHits);
|
|
print(" drop-fail="); printUInt64(fileCache.DropFailures); print("\n");
|
|
print("Filesystem:\n");
|
|
print(" path cache: lookups="); printUInt64(filesystem.PathLookups);
|
|
print(" hits="); printUInt64(filesystem.PathCacheHits);
|
|
print(" misses="); printUInt64(filesystem.PathCacheMisses);
|
|
print(" stores="); printUInt64(filesystem.PathCacheStores); print("\n");
|
|
print(" invalidation: count="); printUInt64(filesystem.PathCacheInvalidations);
|
|
print(" generation="); printUInt64(filesystem.PathCacheGeneration); print("\n");
|
|
print(" directory search: searches="); printUInt64(filesystem.DirectorySearches);
|
|
print(" blocks="); printUInt64(filesystem.DirectoryBlocksRead);
|
|
print(" entries="); printUInt64(filesystem.DirectoryEntriesScanned); print("\n");
|
|
print(" data read spans: runs="); printUInt64(filesystem.DataReadRuns);
|
|
print(" sectors="); printUInt64(filesystem.DataReadSectors);
|
|
print(" merges="); printUInt64(filesystem.DataReadRunMerges);
|
|
print(" max-sectors="); printUInt64(filesystem.MaxDataReadSpanSectors); print("\n");
|
|
|
|
print("Disk I/O:\n");
|
|
print(" devices: registered="); printUInt64(disk.RegisteredDisks); print("\n");
|
|
print(" reads: requests="); printUInt64(disk.ReadRequests);
|
|
print(" sectors="); printUInt64(disk.ReadSectors);
|
|
print(" max-sectors="); printUInt64(disk.MaxReadSectors); print("\n");
|
|
print(" writes: requests="); printUInt64(disk.WriteRequests);
|
|
print(" sectors="); printUInt64(disk.WriteSectors);
|
|
print(" max-sectors="); printUInt64(disk.MaxWriteSectors); print("\n");
|
|
print("Surfaces:\n");
|
|
print(" lifetime: created="); printUInt64(surface.CreatedSurfaces);
|
|
print(" destroyed="); printUInt64(surface.DestroyedSurfaces);
|
|
print(" live="); printUInt64(surface.LiveSurfaces); print("\n");
|
|
print(" live memory: pages="); printUInt64(surface.LivePages);
|
|
print(" bytes="); printUInt64(surface.LiveBytes); print("\n");
|
|
print(" maps/commits: maps="); printUInt64(surface.MapCalls);
|
|
print(" commits="); printUInt64(surface.CommitCalls);
|
|
print(" input-rects="); printUInt64(surface.CommitRectInputs); print("\n");
|
|
print(" commit work: clipped="); printUInt64(surface.CommitClippedRects);
|
|
print(" empty="); printUInt64(surface.CommitEmptyClips); print("\n");
|
|
print(" physical present: calls="); printUInt64(surface.PhysicalPresentCalls);
|
|
print(" bytes="); printUInt64(surface.PhysicalPresentBytesCopied); print("\n");
|
|
|
|
print("Page tables:\n");
|
|
print(" leaf mappings: "); printUInt64(pte.CurrentLeafMappings); print("\n");
|
|
print(" page-table pages: "); printUInt64(pte.PageTablePagesAllocated); print("\n");
|
|
print(" map/unmap: map="); printUInt64(pte.MapCalls);
|
|
print(" failed="); printUInt64(pte.MapFailures);
|
|
print(" unmap="); printUInt64(pte.UnmapCalls);
|
|
print(" failed="); printUInt64(pte.UnmapFailures); print("\n");
|
|
print(" protect: calls="); printUInt64(pte.ProtectCalls);
|
|
print(" failed="); printUInt64(pte.ProtectFailures); print("\n");
|
|
print(" query: total="); printUInt64(pte.QueryCalls);
|
|
print(" failed="); printUInt64(pte.QueryFailures);
|
|
print(" translate="); printUInt64(pte.TranslateCalls); print("\n");
|
|
print(" generic: "); printUInt64(pte.QueryByPurpose[SYS_PTE_QUERY_GENERIC]);
|
|
print(" failed="); printUInt64(pte.QueryFailuresByPurpose[SYS_PTE_QUERY_GENERIC]); print("\n");
|
|
print(" user-probe: "); printUInt64(pte.QueryByPurpose[SYS_PTE_QUERY_USER_PROBE]);
|
|
print(" failed="); printUInt64(pte.QueryFailuresByPurpose[SYS_PTE_QUERY_USER_PROBE]); print("\n");
|
|
print(" heap: "); printUInt64(pte.QueryByPurpose[SYS_PTE_QUERY_HEAP]);
|
|
print(" failed="); printUInt64(pte.QueryFailuresByPurpose[SYS_PTE_QUERY_HEAP]); print("\n");
|
|
print(" vad: "); printUInt64(pte.QueryByPurpose[SYS_PTE_QUERY_VAD]);
|
|
print(" failed="); printUInt64(pte.QueryFailuresByPurpose[SYS_PTE_QUERY_VAD]); print("\n");
|
|
print(" debug: "); printUInt64(pte.QueryByPurpose[SYS_PTE_QUERY_DEBUG]);
|
|
print(" failed="); printUInt64(pte.QueryFailuresByPurpose[SYS_PTE_QUERY_DEBUG]); print("\n");
|
|
print(" self-test: "); printUInt64(pte.QueryByPurpose[SYS_PTE_QUERY_SELF_TEST]);
|
|
print(" failed="); printUInt64(pte.QueryFailuresByPurpose[SYS_PTE_QUERY_SELF_TEST]); print("\n");
|
|
print(" tlb: local="); printUInt64(pte.LocalFlushes);
|
|
print(" global="); printUInt64(pte.GlobalFlushes);
|
|
print(" new-map-skip="); printUInt64(pte.NewMappingNoFlushes);
|
|
print(" replace-flush="); printUInt64(pte.ReplacedMappingFlushes); print("\n");
|
|
print(" shootdowns: requests="); printUInt64(pte.ShootdownRequests);
|
|
print(" targets="); printUInt64(pte.ShootdownTargetCpus);
|
|
print(" skipped="); printUInt64(pte.ShootdownSkippedNoTargets);
|
|
print(" acks="); printUInt64(pte.ShootdownAcks);
|
|
print(" timeouts="); printUInt64(pte.ShootdownTimeouts); print("\n");
|
|
SysFreeVm(cpuBuffer);
|
|
}
|
|
|
|
// Implements the cmd time operation.
|
|
static void cmd_time(void) {
|
|
SYS_SYSTEM_TIME t;
|
|
uint32_t status = SysGetSystemTime(&t);
|
|
if (status != 0) {
|
|
print("time: failed: ");
|
|
printUInt(status);
|
|
print("\n");
|
|
return;
|
|
}
|
|
|
|
printUIntZeroWidth(t.Year, 4); print("-");
|
|
printUIntZeroWidth(t.Month, 2); print("-");
|
|
printUIntZeroWidth(t.Day, 2); print(" ");
|
|
printUIntZeroWidth(t.Hour, 2); print(":");
|
|
printUIntZeroWidth(t.Minute, 2); print(":");
|
|
printUIntZeroWidth(t.Second, 2); print(".");
|
|
printUIntZeroWidth(t.Millisecond, 3);
|
|
print(" epoch="); printUInt64(t.EpochSeconds);
|
|
print(" flags=0x"); printUInt(t.Flags);
|
|
if (t.Flags & SYS_TIME_FLAG_TZ_KNOWN) {
|
|
print(" tz="); printInt(t.TimeZoneMinutes);
|
|
} else {
|
|
print(" tz=unspecified");
|
|
}
|
|
print("\n");
|
|
}
|
|
|
|
// Prints display Caps.
|
|
static void printDisplayCaps(uint32_t caps) {
|
|
bool any = false;
|
|
if (caps & DISPLAY_CAP_FRAMEBUFFER) { print("framebuffer"); any = true; }
|
|
if (caps & DISPLAY_CAP_SOFTWARE_CURSOR) { if (any) print(","); print("software-cursor"); any = true; }
|
|
if (caps & DISPLAY_CAP_HARDWARE_CURSOR) { if (any) print(","); print("hardware-cursor"); any = true; }
|
|
if (caps & DISPLAY_CAP_ACCEL_FILL) { if (any) print(","); print("accel-fill"); any = true; }
|
|
if (caps & DISPLAY_CAP_ACCEL_COPY) { if (any) print(","); print("accel-copy"); any = true; }
|
|
if (caps & DISPLAY_CAP_PRESENT_SYNC) { if (any) print(","); print("present-sync"); any = true; }
|
|
if (caps & DISPLAY_CAP_VBLANK) { if (any) print(","); print("vblank"); any = true; }
|
|
if (caps & DISPLAY_CAP_3D) { if (any) print(","); print("3d"); any = true; }
|
|
if (!any) print("none");
|
|
}
|
|
|
|
// Prints display Stats.
|
|
static void printDisplayStats(const char* title, const DISPLAY_STATS* st) {
|
|
print(title); print(":\n");
|
|
print(" blit: "); printUInt64(st->BlitCount); print("\n");
|
|
print(" fill: "); printUInt64(st->FillRectCount); print("\n");
|
|
print(" copy: "); printUInt64(st->CopyRectCount); print("\n");
|
|
print(" read: "); printUInt64(st->ReadRectCount); print("\n");
|
|
print(" present: "); printUInt64(st->PresentCount); print(" rects="); printUInt64(st->PresentRectCount);
|
|
print(" direct="); printUInt64(st->DirectPresentCount); print("\n");
|
|
print(" dirty: "); printUInt64(st->DirtyRectCount); print(" all="); printUInt64(st->DirtyAllCount); print("\n");
|
|
print(" bytes: logical="); printUInt64(st->BytesCopied);
|
|
print(" scanout="); printUInt64(st->ScanoutBytes); print("\n");
|
|
print(" sync: "); printUInt64(st->SyncCount); print("\n");
|
|
print(" cursor: move="); printUInt64(st->CursorMoveCount);
|
|
print(" show="); printUInt64(st->CursorShowCount);
|
|
print(" hide="); printUInt64(st->CursorHideCount); print("\n");
|
|
}
|
|
|
|
// Implements the cmd display operation.
|
|
static void cmd_display(void) {
|
|
SYS_DISPLAY_INFO info;
|
|
uint32_t status = SysQueryDisplayInfo(&info);
|
|
if (status != 0) {
|
|
print("display: query failed: ");
|
|
printUInt(status);
|
|
print("\n");
|
|
return;
|
|
}
|
|
|
|
print("Display:\n");
|
|
print(" primary: "); print(info.PrimaryName[0] ? info.PrimaryName : "?"); print("\n");
|
|
print(" devices: "); printUInt(info.DeviceCount); print("\n");
|
|
print(" mode: "); printUInt(info.Mode.Width); print("x"); printUInt(info.Mode.Height);
|
|
print(" stride="); printUInt(info.Mode.Stride); print(" format="); printUInt((uint32_t)info.Mode.Format); print("\n");
|
|
print(" caps: 0x"); printHex32(info.Caps); print(" ["); printDisplayCaps(info.Caps); print("]\n");
|
|
print(" scanout: ");
|
|
print((info.Flags & DISPLAY_INFO_FLAG_PAT_WC_AVAILABLE) ? "PAT WC available" : "PAT WC unavailable");
|
|
print("\n");
|
|
printDisplayStats("Manager counters", &info.ManagerStats);
|
|
printDisplayStats("Provider counters", &info.DeviceStats);
|
|
}
|
|
|
|
typedef struct SHELL_FB_BENCH_RESULT {
|
|
uint32_t Width;
|
|
uint32_t Height;
|
|
uint32_t Frames;
|
|
uint32_t Status;
|
|
uint64_t ElapsedTicks;
|
|
uint64_t Frequency;
|
|
uint64_t CommitBytes;
|
|
} SHELL_FB_BENCH_RESULT;
|
|
|
|
// Computes a decimal fixed-point ratio with one digit after the decimal point.
|
|
static uint64_t shellRatioTenths(uint64_t numerator, uint64_t denominator) {
|
|
if (!denominator)
|
|
return 0;
|
|
if (numerator > UINT64_MAX / 10u)
|
|
return (numerator / denominator) * 10u;
|
|
return (numerator * 10u) / denominator;
|
|
}
|
|
|
|
// Fills a benchmark surface with a deterministic high-contrast pattern.
|
|
static void shellFillBenchmarkSurface(uint32_t* pixels,
|
|
uint32_t pitchPixels,
|
|
uint32_t width,
|
|
uint32_t height) {
|
|
for (uint32_t y = 0; y < height; y++) {
|
|
uint32_t* row = pixels + (size_t)y * pitchPixels;
|
|
for (uint32_t x = 0; x < width; x++) {
|
|
uint32_t red = (x * 255u) / (width ? width : 1u);
|
|
uint32_t green = (y * 255u) / (height ? height : 1u);
|
|
uint32_t blue = ((x ^ y) & 0xFFu);
|
|
row[x] = (red << 16) | (green << 8) | blue;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Runs one synchronous surface-commit benchmark case.
|
|
static void shellRunFramebufferBenchmark(uint32_t width,
|
|
uint32_t height,
|
|
uint32_t frames,
|
|
uint64_t frequency,
|
|
SHELL_FB_BENCH_RESULT* result) {
|
|
memset(result, 0, sizeof(*result));
|
|
result->Width = width;
|
|
result->Height = height;
|
|
result->Frames = frames;
|
|
result->Frequency = frequency;
|
|
|
|
uint32_t surface = 0;
|
|
SYS_CREATE_SURFACE_REQUEST create;
|
|
memset(&create, 0, sizeof(create));
|
|
create.Size = sizeof(create);
|
|
create.Width = width;
|
|
create.Height = height;
|
|
create.Format = SYS_SURFACE_FORMAT_XRGB8888;
|
|
create.Flags = SYS_SURFACE_FLAG_NONE;
|
|
create.HandleOut = &surface;
|
|
|
|
uint32_t status = SysCreateSurface(&create);
|
|
if (status != NTLSTATUS_SUCCESS || !surface) {
|
|
result->Status = status ? status : NTLSTATUS_FAILURE;
|
|
return;
|
|
}
|
|
|
|
void* base = 0;
|
|
uint32_t pitchBytes = 0;
|
|
status = SysMapSurface(surface, SYS_SURFACE_MAP_READ | SYS_SURFACE_MAP_WRITE,
|
|
&base, &pitchBytes);
|
|
if (status != NTLSTATUS_SUCCESS || !base || pitchBytes < width * sizeof(uint32_t)) {
|
|
result->Status = status ? status : NTLSTATUS_FAILURE;
|
|
SysCloseHandle(surface);
|
|
return;
|
|
}
|
|
|
|
shellFillBenchmarkSurface((uint32_t*)base, pitchBytes / sizeof(uint32_t), width, height);
|
|
|
|
DISPLAY_RECT rect = { 0, 0, (int32_t)width, (int32_t)height };
|
|
for (uint32_t i = 0; i < 2 && status == NTLSTATUS_SUCCESS; ++i)
|
|
status = SysCommitSurface(surface, &rect, 1, 0);
|
|
|
|
uint64_t begin = 0;
|
|
uint64_t end = 0;
|
|
uint32_t completed = 0;
|
|
if (status == NTLSTATUS_SUCCESS) {
|
|
begin = SysGetTick();
|
|
for (; completed < frames; ++completed) {
|
|
status = SysCommitSurface(surface, &rect, 1, 0);
|
|
if (status != NTLSTATUS_SUCCESS)
|
|
break;
|
|
}
|
|
end = SysGetTick();
|
|
}
|
|
|
|
result->Frames = completed;
|
|
result->Status = status;
|
|
result->ElapsedTicks = end - begin;
|
|
result->CommitBytes = (uint64_t)width * (uint64_t)height * sizeof(uint32_t) * completed;
|
|
|
|
(void)SysUnmapSurface(base);
|
|
SysCloseHandle(surface);
|
|
}
|
|
|
|
// Prints one framebuffer benchmark result.
|
|
static void shellPrintFramebufferBenchmark(const char* name,
|
|
const SHELL_FB_BENCH_RESULT* result) {
|
|
print(" "); print(name); print(" ");
|
|
printUInt(result->Width); print("x"); printUInt(result->Height); print(": ");
|
|
if (result->Status != NTLSTATUS_SUCCESS || !result->ElapsedTicks || !result->Frequency) {
|
|
print("failed status=0x"); printHex32(result->Status); print("\n");
|
|
return;
|
|
}
|
|
|
|
uint64_t elapsedUs = SysTicksToMicroseconds(result->ElapsedTicks, result->Frequency);
|
|
uint64_t fpsTenths = shellRatioTenths((uint64_t)result->Frames * result->Frequency,
|
|
result->ElapsedTicks);
|
|
uint64_t mibTenths = shellRatioTenths(result->CommitBytes * result->Frequency,
|
|
result->ElapsedTicks * 1024u * 1024u);
|
|
|
|
print("frames="); printUInt(result->Frames);
|
|
print(" time="); printUInt64(elapsedUs / 1000u); print(" ms");
|
|
print(" fps="); printUInt64(fpsTenths / 10u); print("."); printUInt64(fpsTenths % 10u);
|
|
print(" payload="); printUInt64(mibTenths / 10u); print("."); printUInt64(mibTenths % 10u); print(" MiB/s");
|
|
print(" bytes="); printUInt64(result->CommitBytes);
|
|
print("\n");
|
|
}
|
|
|
|
// Benchmarks surface commit bookkeeping without implying physical presentation.
|
|
static void cmd_surfbench(void) {
|
|
SYS_DISPLAY_INFO info;
|
|
uint32_t status = SysQueryDisplayInfo(&info);
|
|
if (status != NTLSTATUS_SUCCESS) {
|
|
print("surfbench: display query failed status=0x"); printHex32(status); print("\n");
|
|
return;
|
|
}
|
|
|
|
uint64_t frequency = 0;
|
|
status = SysGetTickFrequency(&frequency);
|
|
if (status != NTLSTATUS_SUCCESS || !frequency) {
|
|
print("surfbench: timer query failed status=0x"); printHex32(status); print("\n");
|
|
return;
|
|
}
|
|
|
|
uint32_t doomWidth = info.Mode.Width < 960u ? info.Mode.Width : 960u;
|
|
uint32_t doomHeight = info.Mode.Height < 600u ? info.Mode.Height : 600u;
|
|
uint32_t fullWidth = info.Mode.Width;
|
|
uint32_t fullHeight = info.Mode.Height;
|
|
|
|
uint64_t targetBytes = 128ull * 1024ull * 1024ull;
|
|
uint64_t doomFrameBytes = (uint64_t)doomWidth * doomHeight * sizeof(uint32_t);
|
|
uint64_t fullFrameBytes = (uint64_t)fullWidth * fullHeight * sizeof(uint32_t);
|
|
uint32_t doomFrames = doomFrameBytes ? (uint32_t)((targetBytes + doomFrameBytes - 1u) / doomFrameBytes) : 1u;
|
|
uint32_t fullFrames = fullFrameBytes ? (uint32_t)((targetBytes + fullFrameBytes - 1u) / fullFrameBytes) : 1u;
|
|
if (doomFrames < 8u) doomFrames = 8u;
|
|
if (doomFrames > 128u) doomFrames = 128u;
|
|
if (fullFrames < 8u) fullFrames = 8u;
|
|
if (fullFrames > 64u) fullFrames = 64u;
|
|
|
|
print("surfbench: commit notifications only; scanout is DWM-owned\n");
|
|
SHELL_FB_BENCH_RESULT doom;
|
|
SHELL_FB_BENCH_RESULT full;
|
|
shellRunFramebufferBenchmark(doomWidth, doomHeight, doomFrames, frequency, &doom);
|
|
shellRunFramebufferBenchmark(fullWidth, fullHeight, fullFrames, frequency, &full);
|
|
|
|
NtlConsoleClear(SYS_HANDLE_INVALID, 0x00012456u);
|
|
print("Framebuffer benchmark:\n");
|
|
print(" provider: "); print(info.PrimaryName[0] ? info.PrimaryName : "?");
|
|
print(" mode="); printUInt(info.Mode.Width); print("x"); printUInt(info.Mode.Height);
|
|
print(" stride="); printUInt(info.Mode.Stride);
|
|
print(" cache="); print((info.Flags & DISPLAY_INFO_FLAG_PAT_WC_AVAILABLE) ? "PAT-WC" : "fallback");
|
|
print("\n");
|
|
shellPrintFramebufferBenchmark("doom", &doom);
|
|
shellPrintFramebufferBenchmark("full", &full);
|
|
}
|
|
|
|
// Implements the cmd mem operation.
|
|
static void cmd_mem(void) {
|
|
SYS_MEMORY_STATUS st;
|
|
if (SysGetMemoryStatus(&st) != 0) {
|
|
print("mem: failed\n");
|
|
return;
|
|
}
|
|
|
|
print("Memory Status:\n");
|
|
print(" Free physical: ");
|
|
printSizeKiB(st.FreePhysicalBytes);
|
|
print("\n Reclaimable: ");
|
|
printSizeKiB(st.ReclaimablePhysicalBytes);
|
|
print("\n Available physical: ");
|
|
printSizeKiB(st.AvailablePhysicalBytes);
|
|
print("\n Pinned physical: ");
|
|
printSizeKiB(st.PinnedPhysicalBytes);
|
|
print("\n Non-evictable: ");
|
|
printSizeKiB(st.NonEvictablePhysicalBytes);
|
|
print("\n User frames: ");
|
|
printSizeKiB(st.UserPhysicalBytes);
|
|
print("\n Page tables: ");
|
|
printSizeKiB(st.PageTablePhysicalBytes);
|
|
print("\n Kernel pool: ");
|
|
printSizeKiB(st.KernelPoolBytes);
|
|
print("\n User heap: ");
|
|
printSizeKiB(st.UserHeapBytes);
|
|
print("\n");
|
|
}
|
|
|
|
// Processes state Name.
|
|
static const char* processStateName(uint32_t state) {
|
|
switch (state) {
|
|
case SYS_PROCESS_STATE_RUNNING: return "run";
|
|
case SYS_PROCESS_STATE_TERMINATING: return "term";
|
|
case SYS_PROCESS_STATE_EXITED: return "exit";
|
|
case SYS_PROCESS_STATE_CONSTRUCTING: return "build";
|
|
default: return "?";
|
|
}
|
|
}
|
|
|
|
// Names public thread states.
|
|
static const char* threadStateName(uint32_t state) {
|
|
switch (state) {
|
|
case SYS_THREAD_STATE_READY: return "ready";
|
|
case SYS_THREAD_STATE_RUNNING: return "run";
|
|
case SYS_THREAD_STATE_WAITING: return "wait";
|
|
case SYS_THREAD_STATE_DEAD: return "dead";
|
|
case SYS_THREAD_STATE_SUSPENDED: return "suspend";
|
|
default: return "?";
|
|
}
|
|
}
|
|
|
|
// Names public wait object types.
|
|
static const char* threadWaitName(uint32_t waitType) {
|
|
switch (waitType) {
|
|
case SYS_THREAD_WAIT_NONE: return "-";
|
|
case SYS_THREAD_WAIT_PROCESS: return "process";
|
|
case SYS_THREAD_WAIT_THREAD: return "thread";
|
|
case SYS_THREAD_WAIT_MUTEX: return "mutex";
|
|
case SYS_THREAD_WAIT_SEMAPHORE: return "sem";
|
|
case SYS_THREAD_WAIT_EVENT: return "event";
|
|
case SYS_THREAD_WAIT_QUEUE: return "queue";
|
|
case SYS_THREAD_WAIT_FILE: return "file";
|
|
case SYS_THREAD_WAIT_DIR: return "dir";
|
|
case SYS_THREAD_WAIT_ASYNC_IO: return "async";
|
|
case SYS_THREAD_WAIT_SOCKET: return "socket";
|
|
case SYS_THREAD_WAIT_DEVICE: return "device";
|
|
case SYS_THREAD_WAIT_DRIVER: return "driver";
|
|
case SYS_THREAD_WAIT_USB_DEVICE: return "usbdev";
|
|
case SYS_THREAD_WAIT_USB_PIPE: return "usbpipe";
|
|
case SYS_THREAD_WAIT_FILE_MAPPING: return "mapping";
|
|
case SYS_THREAD_WAIT_SURFACE: return "surface";
|
|
case SYS_THREAD_WAIT_PIPE: return "pipe";
|
|
default: return "unknown";
|
|
}
|
|
}
|
|
|
|
// Prints thread status flags.
|
|
static void printThreadFlags(uint32_t flags) {
|
|
char buf[8];
|
|
size_t n = 0;
|
|
if (flags & SYS_THREAD_FLAG_CURRENT) buf[n++] = '*';
|
|
if (flags & SYS_THREAD_FLAG_IDLE) buf[n++] = 'I';
|
|
if (flags & SYS_THREAD_FLAG_KERNEL) buf[n++] = 'K';
|
|
if (flags & SYS_THREAD_FLAG_QUEUED) buf[n++] = 'Q';
|
|
if (flags & SYS_THREAD_FLAG_ALERTABLE_WAIT) buf[n++] = 'A';
|
|
if (flags & SYS_THREAD_FLAG_KERNEL_APC_PENDING) buf[n++] = 'k';
|
|
if (flags & SYS_THREAD_FLAG_USER_APC_PENDING) buf[n++] = 'u';
|
|
if (n == 0) buf[n++] = '-';
|
|
buf[n] = '\0';
|
|
printStringWidth(buf, 7);
|
|
}
|
|
|
|
// Implements the cmd ps operation.
|
|
static void cmd_ps(void) {
|
|
SYS_PROCESS_INFO procs[MAX_PROCS];
|
|
size_t count = 0;
|
|
|
|
uint32_t status = SysGetProcessList(procs, MAX_PROCS, &count);
|
|
if (status != 0) {
|
|
print("ps: failed to retrieve process list\n");
|
|
return;
|
|
}
|
|
|
|
print("Processes (Count: ");
|
|
printUInt((uint32_t)count);
|
|
print("):\n");
|
|
print(" PID State Thr Hnd UserKiB ImageKiB Flags Name\n");
|
|
print(" Flags: S=system I=idle P=protected *=current T=terminating X=exited.\n");
|
|
|
|
size_t displayCount = count < MAX_PROCS ? count : MAX_PROCS;
|
|
for (size_t i = 0; i < displayCount; i++) {
|
|
SYS_PROCESS_INFO* p = &procs[i];
|
|
char flags[7];
|
|
size_t flagLen = 0;
|
|
|
|
if (p->Flags & SYS_PROCESS_FLAG_SYSTEM) flags[flagLen++] = 'S';
|
|
if (p->Flags & SYS_PROCESS_FLAG_IDLE) flags[flagLen++] = 'I';
|
|
if (p->Flags & SYS_PROCESS_FLAG_TERMINATION_PROTECTED) flags[flagLen++] = 'P';
|
|
if (p->Flags & SYS_PROCESS_FLAG_CURRENT) flags[flagLen++] = '*';
|
|
if (p->Flags & SYS_PROCESS_FLAG_TERMINATING) flags[flagLen++] = 'T';
|
|
if (p->Flags & SYS_PROCESS_FLAG_EXITED) flags[flagLen++] = 'X';
|
|
if (flagLen == 0) flags[flagLen++] = '-';
|
|
flags[flagLen] = '\0';
|
|
|
|
print(" ");
|
|
printUIntWidth(p->ProcessId, 3);
|
|
print(" ");
|
|
printStringWidth(processStateName(p->State), 5);
|
|
print(" ");
|
|
printUIntWidth(p->ThreadCount, 3);
|
|
print(" ");
|
|
printUIntWidth(p->HandleCount, 3);
|
|
print(" ");
|
|
printUInt64Width((uint64_t)p->UserCommittedBytes / 1024u, 7);
|
|
print(" ");
|
|
printUInt64Width((uint64_t)p->ImageSize / 1024u, 8);
|
|
print(" ");
|
|
printStringWidth(flags, 5);
|
|
print(" ");
|
|
print(p->Name);
|
|
print("\n");
|
|
}
|
|
|
|
if (count > MAX_PROCS) {
|
|
print(" ... and ");
|
|
printUInt((uint32_t)(count - MAX_PROCS));
|
|
print(" more processes.\n");
|
|
}
|
|
}
|
|
|
|
// Implements the cmd threads operation.
|
|
static void cmd_threads(const char* args) {
|
|
uint32_t pid = 0;
|
|
if (!parseUInt(args, &pid)) {
|
|
print("threads: usage: threads <pid>\n");
|
|
return;
|
|
}
|
|
|
|
SYS_THREAD_INFO threads[MAX_THREADS];
|
|
size_t count = 0;
|
|
uint32_t status = SysGetThreadList(pid, threads, MAX_THREADS, &count);
|
|
if (status != 0) {
|
|
print("threads: query failed status=");
|
|
printUInt(status);
|
|
print("\n");
|
|
return;
|
|
}
|
|
|
|
print("Threads for PID ");
|
|
printUInt(pid);
|
|
print(" (Count: ");
|
|
printUInt((uint32_t)count);
|
|
print("):\n");
|
|
print(" TID State Base Eff CPU LastCPU QRem QTick Burst Wait WStat Flags Name\n");
|
|
print(" Flags: *=current I=idle K=kernel Q=queued A=alertable k=kernel-apc u=user-apc.\n");
|
|
|
|
size_t displayCount = count < MAX_THREADS ? count : MAX_THREADS;
|
|
for (size_t i = 0; i < displayCount; i++) {
|
|
SYS_THREAD_INFO* t = &threads[i];
|
|
print(" ");
|
|
printUIntWidth(t->ThreadId, 3);
|
|
print(" ");
|
|
printStringWidth(threadStateName(t->State), 5);
|
|
print(" ");
|
|
if (t->BaseSchedulingClass == SYS_SCHEDULING_REALTIME) print("R"); else print("N");
|
|
printUIntWidth(t->BasePriority, 2);
|
|
print(" ");
|
|
if (t->EffectiveSchedulingClass == SYS_SCHEDULING_REALTIME) print("R"); else print("N");
|
|
printUIntWidth(t->EffectivePriority, 2);
|
|
print(" ");
|
|
if (t->RunningCpu == 0xffffffffu) printStringWidth("-", 3);
|
|
else printUIntWidth(t->RunningCpu, 3);
|
|
print(" ");
|
|
if (t->LastCpu == 0xffffffffu) printStringWidth("-", 7);
|
|
else printUIntWidth(t->LastCpu, 7);
|
|
print(" ");
|
|
printUIntWidth(t->QuantumRemaining, 4);
|
|
print(" ");
|
|
printUIntWidth(t->QuantumTicks, 5);
|
|
print(" ");
|
|
printUIntWidth(t->CpuBurstTicks, 5);
|
|
print(" ");
|
|
printStringWidth(threadWaitName(t->WaitObjectType), 8);
|
|
print(" ");
|
|
printUIntWidth(t->WaitStatus, 5);
|
|
print(" ");
|
|
printThreadFlags(t->Flags);
|
|
print(" ");
|
|
print(t->Name[0] ? t->Name : "-");
|
|
print("\n");
|
|
}
|
|
|
|
if (count > MAX_THREADS) {
|
|
print(" ... and ");
|
|
printUInt((uint32_t)(count - MAX_THREADS));
|
|
print(" more threads.\n");
|
|
}
|
|
}
|
|
|
|
static void cmd_kill(const char* args) {
|
|
uint32_t pid = 0;
|
|
if (!parseUInt(args, &pid)) { print("kill: usage: kill <pid>\n"); return; }
|
|
|
|
uint32_t hProc = 0;
|
|
uint32_t status = SysOpenProcess(pid, SYS_PROCESS_RIGHT_TERMINATE, &hProc);
|
|
if (status == 0) {
|
|
status = SysKillProcess(hProc, (int32_t)NTLSTATUS_PROCESS_KILLED);
|
|
SysCloseHandle(hProc);
|
|
}
|
|
|
|
if (status == NTLSTATUS_ACCESS_DENIED) {
|
|
print("kill: process ");
|
|
printUInt(pid);
|
|
print(" is termination-protected.\n");
|
|
} else if (status != 0) {
|
|
print("shell: failed to kill process ");
|
|
printUInt(pid);
|
|
print(", error code: ");
|
|
printUInt(status);
|
|
print("\n");
|
|
} else {
|
|
print("Process ");
|
|
printUInt(pid);
|
|
print(" terminated successfully.\n");
|
|
}
|
|
}
|
|
|
|
// Implements the cmd write operation.
|
|
static void cmd_write(const char* args) {
|
|
char filename[MAX_CMD]; int i = 0;
|
|
while (args[i] != ' ' && args[i] != '\0' && i < MAX_CMD - 1) {
|
|
filename[i] = args[i]; i++;
|
|
}
|
|
filename[i] = '\0';
|
|
if (args[i] == '\0' || args[i+1] == '\0') { print("write: usage: write <filename> <text>\n"); return; }
|
|
uint32_t hFile;
|
|
if (create_file(filename, &hFile) != 0) { print("write: could not create file\n"); return; }
|
|
size_t written;
|
|
SYS_FILE_IO_REQUEST writeReq = SysMakeFileIoRequest(hFile, args + i + 1, strlen(args + i + 1), &written);
|
|
SysWriteFile(&writeReq);
|
|
SysCloseHandle(hFile);
|
|
print("File written successfully.\n");
|
|
}
|
|
|
|
#define SHELL_DIR_INITIAL_CAPACITY 64u
|
|
|
|
// Compares two names using case-insensitive order with a stable case tie-breaker.
|
|
static int shellCompareNames(const char* a, const char* b) {
|
|
size_t i = 0;
|
|
if (!a) a = "";
|
|
if (!b) b = "";
|
|
|
|
while (true) {
|
|
char ca = shellAsciiLower(a[i]);
|
|
char cb = shellAsciiLower(b[i]);
|
|
if (ca != cb)
|
|
return (ca < cb) ? -1 : 1;
|
|
if (ca == '\0')
|
|
break;
|
|
i++;
|
|
}
|
|
|
|
return strcmp(a, b);
|
|
}
|
|
|
|
// Compares two directory entries by display name.
|
|
static int shellCompareDirEntries(const SYS_DIR_ENTRY* a, const SYS_DIR_ENTRY* b) {
|
|
return shellCompareNames(a->Name, b->Name);
|
|
}
|
|
|
|
// Allocates a directory-entry array from VM so large listings do not consume stack.
|
|
static uint32_t shellAllocDirEntries(uint32_t capacity, SYS_DIR_ENTRY** entriesOut) {
|
|
void* base = 0;
|
|
size_t bytes = (size_t)capacity * sizeof(SYS_DIR_ENTRY);
|
|
size_t pages = (bytes + 4095u) / 4096u;
|
|
if (pages == 0) pages = 1;
|
|
|
|
SYS_ALLOC_VM_REQUEST req;
|
|
memset(&req, 0, sizeof(req));
|
|
req.Size = sizeof(req);
|
|
req.Protect = SYS_VM_READWRITE;
|
|
req.Flags = SYS_VM_FLAG_NONE;
|
|
req.PageCount = pages;
|
|
req.BaseOut = &base;
|
|
|
|
uint32_t status = SysAllocVm(&req);
|
|
if (status != 0 || !base) {
|
|
*entriesOut = 0;
|
|
return status ? status : 1;
|
|
}
|
|
|
|
*entriesOut = (SYS_DIR_ENTRY*)base;
|
|
return 0;
|
|
}
|
|
|
|
// Grows a directory-entry array while preserving already-read entries.
|
|
static uint32_t shellGrowDirEntries(SYS_DIR_ENTRY** entries, uint32_t* capacity, uint32_t count) {
|
|
uint32_t oldCapacity = *capacity;
|
|
uint32_t newCapacity = oldCapacity ? oldCapacity * 2u : SHELL_DIR_INITIAL_CAPACITY;
|
|
SYS_DIR_ENTRY* next = 0;
|
|
uint32_t status = shellAllocDirEntries(newCapacity, &next);
|
|
if (status != 0)
|
|
return status;
|
|
|
|
if (*entries && count)
|
|
memcpy(next, *entries, (size_t)count * sizeof(SYS_DIR_ENTRY));
|
|
if (*entries)
|
|
SysFreeVm(*entries);
|
|
|
|
*entries = next;
|
|
*capacity = newCapacity;
|
|
return 0;
|
|
}
|
|
|
|
// Sorts directory entries alphabetically.
|
|
static void shellSortDirEntries(SYS_DIR_ENTRY* entries, uint32_t count) {
|
|
for (uint32_t i = 1; i < count; i++) {
|
|
SYS_DIR_ENTRY current = entries[i];
|
|
uint32_t j = i;
|
|
while (j > 0 && shellCompareDirEntries(¤t, &entries[j - 1]) < 0) {
|
|
entries[j] = entries[j - 1];
|
|
j--;
|
|
}
|
|
entries[j] = current;
|
|
}
|
|
}
|
|
|
|
// Prints one directory entry in cmd dir format.
|
|
static void shellPrintDirEntry(const SYS_DIR_ENTRY* ke) {
|
|
printDirTime(&ke->LastWriteTime);
|
|
|
|
if (ke->Attr & 0x10) {
|
|
print("<DIR> ");
|
|
print(ke->Name);
|
|
} else {
|
|
print(" ");
|
|
print(ke->Name);
|
|
print(" (");
|
|
printUInt64(ke->FileSize);
|
|
print(" bytes)");
|
|
}
|
|
print("\n");
|
|
}
|
|
|
|
// Prints one RSSRV namespace kind label.
|
|
static void shellPrintResourceKind(uint32_t kind) {
|
|
if (kind == NTL_RESOURCE_KIND_NAMESPACE_DIRECTORY) print("<NSDIR> ");
|
|
else if (kind == NTL_RESOURCE_KIND_SYMBOLIC_LINK) print("<SYMLINK> ");
|
|
else if (kind == NTL_RESOURCE_KIND_VOLUME) print("<VOLUME> ");
|
|
else if (kind == NTL_RESOURCE_KIND_DIRECTORY) print("<DIRCAP> ");
|
|
else if (kind == NTL_RESOURCE_KIND_DEVICE) print("<DEVICE> ");
|
|
else print("<OBJECT> ");
|
|
}
|
|
|
|
// Handles dir for an exact RSSRV namespace directory and returns whether it owned the request.
|
|
static bool shellTryResourceDir(const char* target) {
|
|
if (!target || target[0] != '/') return false;
|
|
SYS_HANDLE session = SYS_HANDLE_INVALID;
|
|
if (!KSUCCESS(NtlResourceConnect(&session))) return false;
|
|
|
|
uint32_t kind = NTL_RESOURCE_KIND_INVALID;
|
|
KSTATUS status = NtlResourceQuery(session, SysSvFromCstr(target), &kind);
|
|
if (!KSUCCESS(status) || kind != NTL_RESOURCE_KIND_NAMESPACE_DIRECTORY) {
|
|
(void)NtlResourceClose(session);
|
|
return false;
|
|
}
|
|
|
|
NTL_RESOURCE_ENUM_CURSOR cursor;
|
|
memset(&cursor, 0, sizeof(cursor));
|
|
for (;;) {
|
|
NTL_RESOURCE_ENUM_ENTRY entry;
|
|
status = NtlResourceEnumerate(session, SysSvFromCstr(target), &cursor, &entry);
|
|
if (status == NTLSTATUS_NO_MORE_FILES) break;
|
|
if (!KSUCCESS(status)) {
|
|
print("dir: namespace enumeration failed, status=0x");
|
|
printHex32(status);
|
|
print("\n");
|
|
break;
|
|
}
|
|
shellPrintResourceKind(entry.Kind);
|
|
print(entry.Name);
|
|
if (entry.Kind == NTL_RESOURCE_KIND_NAMESPACE_DIRECTORY) print("/");
|
|
print("\n");
|
|
}
|
|
(void)NtlResourceClose(session);
|
|
return true;
|
|
}
|
|
|
|
// Implements the cmd dir operation.
|
|
static void cmd_dir(const char* path) {
|
|
uint32_t hDir;
|
|
const char* target = (path && *path != '\0') ? path : ".";
|
|
if (shellTryResourceDir(target)) return;
|
|
|
|
uint32_t st = open_dir(target, &hDir);
|
|
if (st != 0) {
|
|
print("dir: failed to open directory, status=0x");
|
|
printHex32(st);
|
|
print("\n");
|
|
return;
|
|
}
|
|
|
|
SYS_DIR_ENTRY* entries = 0;
|
|
uint32_t count = 0;
|
|
uint32_t capacity = 0;
|
|
|
|
while (true) {
|
|
SYS_DIR_ENTRY ke;
|
|
st = SysReadDir(hDir, &ke);
|
|
if (st != 0)
|
|
break;
|
|
|
|
if (count == capacity) {
|
|
st = shellGrowDirEntries(&entries, &capacity, count);
|
|
if (st != 0) {
|
|
print("dir: out of memory, status=0x");
|
|
printHex32(st);
|
|
print("\n");
|
|
SysCloseHandle(hDir);
|
|
if (entries) SysFreeVm(entries);
|
|
return;
|
|
}
|
|
}
|
|
|
|
entries[count++] = ke;
|
|
}
|
|
|
|
SysCloseHandle(hDir);
|
|
shellSortDirEntries(entries, count);
|
|
for (uint32_t i = 0; i < count; i++)
|
|
shellPrintDirEntry(&entries[i]);
|
|
|
|
if (entries)
|
|
SysFreeVm(entries);
|
|
}
|
|
|
|
// Implements the cmd type operation.
|
|
static void cmd_type(const char* filename) {
|
|
uint32_t hFile;
|
|
if (open_file(filename, &hFile) != 0) { print("type: cannot open file\n"); return; }
|
|
char buffer[512]; size_t bytesRead;
|
|
while (true) {
|
|
SYS_FILE_IO_REQUEST readReq = SysMakeFileIoRequest(hFile, buffer, sizeof(buffer) - 1, &bytesRead);
|
|
if (SysReadFile(&readReq) != 0 || bytesRead == 0) break;
|
|
buffer[bytesRead] = '\0'; print(buffer);
|
|
}
|
|
SysCloseHandle(hFile); print("\n");
|
|
}
|
|
|
|
// Implements the extract Program Path operation.
|
|
static bool extractProgramPath(const char* cmdline, char* out, size_t outSize) {
|
|
if (!cmdline || !out || outSize == 0) return false;
|
|
|
|
while (*cmdline == ' ' || *cmdline == '\t') cmdline++;
|
|
if (*cmdline == '\0') return false;
|
|
|
|
size_t pos = 0;
|
|
if (*cmdline == '"') {
|
|
cmdline++;
|
|
while (*cmdline && *cmdline != '"' && pos + 1 < outSize)
|
|
out[pos++] = *cmdline++;
|
|
} else {
|
|
while (*cmdline && *cmdline != ' ' && *cmdline != '\t' && pos + 1 < outSize)
|
|
out[pos++] = *cmdline++;
|
|
}
|
|
|
|
out[pos] = '\0';
|
|
return pos != 0;
|
|
}
|
|
|
|
// Returns whether one input key is currently held.
|
|
static bool shellIsKeyDown(INPUT_KEY key) {
|
|
bool down = false;
|
|
return NtlInputIsKeyDown(key, &down) == NTLSTATUS_SUCCESS && down;
|
|
}
|
|
|
|
// Waits for a foreground process while polling Ctrl-C without consuming child input.
|
|
static uint32_t waitForegroundProcess(uint32_t hProc) {
|
|
bool sentBreak = false;
|
|
|
|
while (true) {
|
|
uint32_t status = SysWaitSingle(hProc,
|
|
SYS_WAIT_FLAG_NONE,
|
|
SHELL_FOREGROUND_POLL_MS);
|
|
if (status == NTLSTATUS_SUCCESS)
|
|
return status;
|
|
if (status != NTLSTATUS_TIMEOUT)
|
|
return status;
|
|
|
|
uint32_t modifiers = NtlInputGetKeyModifiers();
|
|
if (!sentBreak && (modifiers & INPUT_MOD_CTRL) &&
|
|
shellIsKeyDown(INPUT_KEY_C)) {
|
|
print("^C\n");
|
|
uint32_t killStatus = SysKillProcess(
|
|
hProc,
|
|
(int32_t)NTLSTATUS_PROCESS_KILLED);
|
|
if (killStatus != NTLSTATUS_SUCCESS) {
|
|
print("shell: foreground termination failed status=");
|
|
printUInt(killStatus);
|
|
print("\n");
|
|
return killStatus;
|
|
}
|
|
sentBreak = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Run one command.
|
|
static int runCommand(const char* cmd) {
|
|
if (!cmd || *cmd == '\0') return 0;
|
|
if (strcmp(cmd, "exit") == 0) return 1;
|
|
if (strcmp(cmd, "help") == 0) { cmd_help(); return 0; }
|
|
if (strcmp(cmd, "clear") == 0) { (void)NtlConsoleClear(SYS_HANDLE_INVALID, 0x012456); return 0; }
|
|
if (strncmp(cmd, "crash", 5) == 0) {
|
|
// Check the sub-option trailing "crash " (offset 6).
|
|
if (strcmp(cmd + 6, "void") == 0) {
|
|
// Jumping into the void.
|
|
void (*invalid_func)(void) = NULL;
|
|
invalid_func();
|
|
return 0;
|
|
}
|
|
else if (strcmp(cmd + 6, "noncanonical") == 0) {
|
|
// Non-canonical pointer dereference.
|
|
volatile int* ptr = (volatile int*)0x4242424242424242ULL;
|
|
*ptr = 1337;
|
|
return 0;
|
|
}
|
|
else if (strcmp(cmd + 6, "ud2") == 0) {
|
|
__ud2();
|
|
return 0;
|
|
}
|
|
else if (strcmp(cmd + 6, "nx") == 0) {
|
|
// NX / DEP Violation.
|
|
unsigned char code[] = { 0xC3 };
|
|
void (*execute_stack)(void) = (void (*)(void))code;
|
|
execute_stack();
|
|
return 0;
|
|
}
|
|
else if (strcmp(cmd + 6, "divzero") == 0) {
|
|
// Division by zero.
|
|
volatile int zero = 0;
|
|
volatile int result = 1 / zero;
|
|
(void)result;
|
|
return 0;
|
|
}
|
|
else {
|
|
// Default fallback if just "crash" or an unknown sub-option is passed.
|
|
*(volatile int*)0 = 42;
|
|
return 0;
|
|
}
|
|
}
|
|
if (strcmp(cmd, "mem") == 0) { cmd_mem(); return 0; }
|
|
if (strcmp(cmd, "time") == 0 || strcmp(cmd, "date") == 0) { cmd_time(); return 0; }
|
|
if (strcmp(cmd, "display") == 0 || strcmp(cmd, "gpu") == 0) { cmd_display(); return 0; }
|
|
if (strcmp(cmd, "surfbench") == 0) { cmd_surfbench(); return 0; }
|
|
if (strcmp(cmd, "volume") == 0) { cmd_volume(""); return 0; }
|
|
if (strncmp(cmd, "volume ", 7) == 0) { cmd_volume(cmd + 7); return 0; }
|
|
if (strcmp(cmd, "stats") == 0 || strcmp(cmd, "telemetry") == 0) { cmd_stats(); return 0; }
|
|
if (strcmp(cmd, "ps") == 0) { cmd_ps(); return 0; }
|
|
if (strncmp(cmd, "threads ", 8) == 0) { cmd_threads(cmd + 8); return 0; }
|
|
if (strncmp(cmd, "kill ", 5) == 0) { cmd_kill(cmd + 5); return 0; }
|
|
if (strcmp(cmd, "dir") == 0) { cmd_dir("."); return 0; }
|
|
if (strncmp(cmd, "dir ", 4) == 0) { cmd_dir(cmd + 4); return 0; }
|
|
if (strncmp(cmd, "cd ", 3) == 0) { if (set_cwd(cmd + 3) != 0) print("cd: failed\n"); return 0; }
|
|
if (strncmp(cmd, "del ", 4) == 0) { delete_file(cmd + 4); return 0; }
|
|
if (strncmp(cmd, "type ", 5) == 0) { cmd_type(cmd + 5); return 0; }
|
|
if (strncmp(cmd, "write ", 6) == 0) { cmd_write(cmd + 6); return 0; }
|
|
|
|
// Make a local copy of the command line so we can mutate it safely if needed
|
|
char modifiedCmd[MAX_CMD];
|
|
strncpy(modifiedCmd, cmd, sizeof(modifiedCmd) - 1);
|
|
modifiedCmd[sizeof(modifiedCmd) - 1] = '\0';
|
|
|
|
// Parse background indicator execution flag (&)
|
|
bool background = false;
|
|
size_t cmdLen = strlen(modifiedCmd);
|
|
if (cmdLen > 0 && modifiedCmd[cmdLen - 1] == '&') {
|
|
background = true;
|
|
modifiedCmd[cmdLen - 1] = '\0'; // Strip the ampersand
|
|
|
|
// Clean up any trailing whitespace left before the ampersand
|
|
cmdLen = strlen(modifiedCmd);
|
|
while (cmdLen > 0 && (modifiedCmd[cmdLen - 1] == ' ' || modifiedCmd[cmdLen - 1] == '\t')) {
|
|
modifiedCmd[cmdLen - 1] = '\0';
|
|
cmdLen--;
|
|
}
|
|
}
|
|
|
|
char program[MAX_CMD];
|
|
if (!extractProgramPath(modifiedCmd, program, sizeof(program))) return 0;
|
|
|
|
uint32_t hProc;
|
|
if (create_process(program, &hProc, modifiedCmd) == 0) {
|
|
if (background) {
|
|
// Background mode: Clean up the handle immediately and skip waiting.
|
|
print("[Launched in background, handle: ");
|
|
printUInt(hProc);
|
|
print("]\n");
|
|
SysCloseHandle(hProc);
|
|
} else {
|
|
// Foreground mode: Wait for completion and flush standard keyboard stream.
|
|
uint32_t waitStatus = waitForegroundProcess(hProc);
|
|
if (waitStatus != NTLSTATUS_SUCCESS) {
|
|
print("shell: wait failed: ");
|
|
printUInt(waitStatus);
|
|
print("\n");
|
|
}
|
|
print("Process exited with code: ");
|
|
int32_t exitCode;
|
|
SysGetProcessExitCode(hProc, &exitCode);
|
|
printInt(exitCode);
|
|
const char* exitName = processExitStatusName((uint32_t)exitCode);
|
|
if (exitName) {
|
|
print(" (0x");
|
|
printHex32((uint32_t)exitCode);
|
|
print(" ");
|
|
print(exitName);
|
|
print(")");
|
|
}
|
|
print("\n");
|
|
SysCloseHandle(hProc);
|
|
INPUT_KEY drainedKey;
|
|
while (NtlInputPollKeyboard(&drainedKey) == NTLSTATUS_SUCCESS) (void)NtlInputReadKey(&drainedKey);
|
|
}
|
|
} else {
|
|
print("shell: launch failed\n");
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
// Runs the requested operation.
|
|
int main(void) {
|
|
const char* cmdline = SysGetPeb()->CommandLine.Data;
|
|
char lineBuf[MAX_CMD];
|
|
|
|
print("shell: command line: ");
|
|
print(cmdline);
|
|
print("\n");
|
|
print("shell: ready\n");
|
|
while (true) {
|
|
print(SHELL_PROMPT);
|
|
if (readLine(lineBuf, MAX_CMD)) continue;
|
|
char* cmd = trim(lineBuf);
|
|
shellHistoryAdd(cmd);
|
|
if (runCommand(cmd)) break;
|
|
}
|
|
print("shell: goodbye\n");
|
|
SysExitProcess(0);
|
|
return 0;
|
|
}
|