diff --git a/Makefile b/Makefile index 528e06a..3bc1bce 100644 --- a/Makefile +++ b/Makefile @@ -125,7 +125,7 @@ CORE_SRCS = kernel/main.c kernel/task.c kernel/sync.c kernel/timer.c kernel/prin kernel/panic.c kernel/trace.c kernel/sched.c kernel/cron.c kernel/syscall.c \ kernel/syslog.c kernel/module.c kernel/kobject.c kernel/metrics.c kernel/namespace.c \ kernel/signal.c kernel/workqueue.c kernel/tsc.c kernel/vt_art.c \ - kernel/sub/sub_runtime.c kernel/sub/sub_vm.c kernel/nt/ob.c \ + kernel/sub/sub_runtime.c kernel/sub/sub_vm.c kernel/nt/ob.c kernel/nt/reg.c \ kernel/wait.c kernel/futex.c kernel/rcu.c kernel/ktest.c \ kernel/elf.c kernel/exec.c \ lib/string.c lib/vsprintf.c lib/bitmap.c lib/rbtree.c lib/kfifo.c lib/hashtable.c lib/font8x8.c \ diff --git a/gui/gui_apps.c b/gui/gui_apps.c index 0d7b884..c99507d 100644 --- a/gui/gui_apps.c +++ b/gui/gui_apps.c @@ -9,6 +9,7 @@ #include #include #include +#include #include #include @@ -649,3 +650,121 @@ void gui_app_rustlab_launch(int x, int y, int w, int h) { win->handle_event = rustlab_event; } } + +// ================================================================= +// 9. NT Object Browser -- a WinObj-style view of the object namespace +// ================================================================= +typedef struct { ob_object_t* obj; int depth; } objrow_t; +#define OBJBROWSE_MAX_ROWS 96 + +typedef struct { + int scroll; +} objbrowse_data_t; + +static int objbrowse_flatten(ob_object_t* obj, int depth, objrow_t* rows, int max, int count) { + if (!obj || count >= max) return count; + rows[count].obj = obj; + rows[count].depth = depth; + count++; + if (obj->type == OB_TYPE_DIRECTORY) { + int c = ob_dir_child_count(obj); + for (int i = 0; i < c && count < max; i++) { + count = objbrowse_flatten(ob_dir_child(obj, i), depth + 1, rows, max, count); + } + } + return count; +} + +static uint32_t objbrowse_type_color(ob_type_t t) { + switch (t) { + case OB_TYPE_DIRECTORY: return GUI_THEME_PRIMARY; + case OB_TYPE_EVENT: return GUI_THEME_SUCCESS; + case OB_TYPE_SEMAPHORE: return GUI_THEME_WARNING; + case OB_TYPE_MUTANT: return GUI_THEME_ACCENT; + default: return GUI_THEME_TEXT_MUTED; + } +} + +static void objbrowse_paint(gui_window_t* win) { + objbrowse_data_t* bd = (objbrowse_data_t*)win->user_data; + if (!bd) return; + + int cx = win->x + 12; + int top = win->y + GUI_TITLEBAR_HEIGHT; + + // Header + live stats + uint32_t objs = 0, handles = 0, per[OB_TYPE_MAX]; + ob_get_stats(&objs, &handles, per); + gui_gfx_draw_string_16_shadow(cx, top + 8, "NT Object Namespace \\", GUI_THEME_PRIMARY, GUI_COLOR_BLACK); + char hdr[80]; + snprintf(hdr, sizeof(hdr), "%u objects %u handles open (scroll: click top/bottom)", objs, handles); + gui_gfx_draw_string(cx, top + 28, hdr, GUI_THEME_TEXT_MUTED); + + // Flatten the live namespace + objrow_t rows[OBJBROWSE_MAX_ROWS]; + int total = objbrowse_flatten(ob_root_directory(), 0, rows, OBJBROWSE_MAX_ROWS, 0); + + int list_top = top + 44; + int row_h = 13; + int visible = (win->height - (list_top - win->y) - 10) / row_h; + if (visible < 1) visible = 1; + + if (bd->scroll > total - visible) bd->scroll = total - visible; + if (bd->scroll < 0) bd->scroll = 0; + + for (int i = 0; i < visible && (bd->scroll + i) < total; i++) { + objrow_t* r = &rows[bd->scroll + i]; + ob_object_t* o = r->obj; + int y = list_top + i * row_h; + int x = cx + r->depth * 14; + + char line[96]; + if (o->type == OB_TYPE_DIRECTORY) { + snprintf(line, sizeof(line), "%s\\", o->name); + } else { + snprintf(line, sizeof(line), "%s", o->name); + } + gui_gfx_draw_string(x, y, line, objbrowse_type_color(o->type)); + + char meta[48]; + snprintf(meta, sizeof(meta), "%-9s r=%d h=%d", ob_type_name(o->type), o->ref_count, o->handle_count); + gui_gfx_draw_string(win->x + win->width - 180, y, meta, GUI_THEME_TEXT_DIM); + } + + // Scroll indicator + if (total > visible) { + char sc[32]; + snprintf(sc, sizeof(sc), "%d-%d / %d", bd->scroll + 1, + (bd->scroll + visible < total) ? bd->scroll + visible : total, total); + gui_gfx_draw_string(win->x + win->width - 90, top + 28, sc, GUI_THEME_TEXT_DIM); + } +} + +static void objbrowse_event(gui_window_t* win, const gui_event_t* ev) { + objbrowse_data_t* bd = (objbrowse_data_t*)win->user_data; + if (!bd) return; + + if (ev->type == GUI_EVENT_CLOSE) { + if (win->user_data) { kfree(win->user_data); win->user_data = NULL; } + return; + } + if (ev->type != GUI_EVENT_MOUSE_DOWN) return; + + // Top third scrolls up, bottom third scrolls down. + if (ev->rel_y < win->height / 3) { + bd->scroll -= 3; + } else if (ev->rel_y > (win->height * 2) / 3) { + bd->scroll += 3; + } + if (bd->scroll < 0) bd->scroll = 0; +} + +void gui_app_objbrowse_launch(int x, int y, int w, int h) { + gui_window_t* win = gui_wm_create_window("Object Browser", x, y, (w > 0) ? w : 460, (h > 0) ? h : 340); + if (win) { + objbrowse_data_t* bd = (objbrowse_data_t*)kzalloc(sizeof(objbrowse_data_t)); + win->user_data = bd; + win->paint = objbrowse_paint; + win->handle_event = objbrowse_event; + } +} diff --git a/gui/gui_desktop.c b/gui/gui_desktop.c index ece9e51..d33ab0a 100644 --- a/gui/gui_desktop.c +++ b/gui/gui_desktop.c @@ -58,6 +58,7 @@ static const desktop_app_t g_apps[] = { { "Paint", GUI_ICON_PAINT, gui_app_paint_launch, 200, 60, 360, 260 }, { "Life", GUI_ICON_FLASK, gui_app_life_launch, 240, 70, 480, 380 }, { "Rust Lab", GUI_ICON_INFO, gui_app_rustlab_launch, 300, 80, 420, 300 }, + { "Objects", GUI_ICON_TASKS, gui_app_objbrowse_launch,280, 70, 460, 340 }, { "Clock", GUI_ICON_CLOCK, gui_app_clock_launch, 460, 120, 280, 260 }, { "Settings", GUI_ICON_SETTINGS, gui_app_settings_launch, 160, 60, 400, 320 }, { "About", GUI_ICON_INFO, gui_app_about_launch, 240, 140, 330, 170 }, @@ -66,8 +67,8 @@ static const desktop_app_t g_apps[] = { // Which of the above appear on the desktop itself. // Indices into g_apps[]: Terminal, Files, Monitor, Kernel Log, KTest, Life, -// Rust Lab, Settings. -static const int g_desktop_icons[] = { 0, 1, 2, 4, 5, 9, 10, 12 }; +// Rust Lab, Settings. (Objects lives in the start menu.) +static const int g_desktop_icons[] = { 0, 1, 2, 4, 5, 9, 10, 13 }; #define DESKTOP_ICON_COUNT ((int)(sizeof(g_desktop_icons) / sizeof(g_desktop_icons[0]))) static bool g_start_menu_open = false; diff --git a/include/gui/gui_apps.h b/include/gui/gui_apps.h index 4c99a0e..a779936 100644 --- a/include/gui/gui_apps.h +++ b/include/gui/gui_apps.h @@ -11,5 +11,6 @@ void gui_app_paint_launch(int x, int y, int w, int h); void gui_app_about_launch(int x, int y, int w, int h); void gui_app_life_launch(int x, int y, int w, int h); void gui_app_rustlab_launch(int x, int y, int w, int h); +void gui_app_objbrowse_launch(int x, int y, int w, int h); #endif // _GUI_APPS_H diff --git a/include/kernel/nt/reg.h b/include/kernel/nt/reg.h new file mode 100644 index 0000000..b9ce64d --- /dev/null +++ b/include/kernel/nt/reg.h @@ -0,0 +1,77 @@ +#ifndef _KERNEL_NT_REG_H +#define _KERNEL_NT_REG_H + +// ----------------------------------------------------------------------------- +// SUB-OS NT-Inspired Configuration Registry +// +// A hive-and-key configuration store modelled on the Windows NT Configuration +// Manager (Cm). Keys form a tree rooted at "\Registry"; each key holds typed +// values (REG_SZ / REG_DWORD / REG_QWORD / REG_BINARY / REG_MULTI_SZ) whose +// type codes match the Win32 constants. Backed by kmalloc, bounded fan-out. +// ----------------------------------------------------------------------------- + +#include +#include +#include + +typedef enum { + REG_NONE = 0, + REG_SZ = 1, // null-terminated string + REG_BINARY = 3, // raw bytes + REG_DWORD = 4, // 32-bit + REG_MULTI_SZ = 7, // list of strings, shown joined by '|' + REG_QWORD = 11 // 64-bit +} reg_type_t; + +#define REG_NAME_MAX 48 +#define REG_STR_MAX 128 +#define REG_MAX_CHILDREN 24 +#define REG_MAX_VALUES 24 + +typedef struct reg_value { + char name[REG_NAME_MAX]; + reg_type_t type; + uint32_t data_len; + union { + uint32_t dword; + uint64_t qword; + char str[REG_STR_MAX]; + uint8_t bin[REG_STR_MAX]; + } data; +} reg_value_t; + +typedef struct reg_key { + char name[REG_NAME_MAX]; + struct reg_key* parent; + struct reg_key* children[REG_MAX_CHILDREN]; + int child_count; + reg_value_t* values[REG_MAX_VALUES]; + int value_count; +} reg_key_t; + +void reg_init(void); +reg_key_t* reg_root(void); +const char* reg_type_name(reg_type_t t); + +// Path form: "\Registry\Machine\Software\SUB-OS". create makes intermediates. +reg_key_t* reg_create_key(const char* path); +reg_key_t* reg_open_key(const char* path); + +// Value setters/getters (operate on an already-open key). +reg_value_t* reg_set_dword(reg_key_t* key, const char* name, uint32_t value); +reg_value_t* reg_set_qword(reg_key_t* key, const char* name, uint64_t value); +reg_value_t* reg_set_sz(reg_key_t* key, const char* name, const char* str); +reg_value_t* reg_query_value(reg_key_t* key, const char* name); + +// Enumeration. +int reg_enum_key_count(reg_key_t* key); +reg_key_t* reg_enum_key(reg_key_t* key, int index); +int reg_enum_value_count(reg_key_t* key); +reg_value_t* reg_enum_value(reg_key_t* key, int index); + +// Render a value's data into a human-readable string (for reg query / GUI). +void reg_value_to_string(const reg_value_t* v, char* out, size_t out_len); + +void reg_get_stats(uint32_t* out_keys, uint32_t* out_values); + +#endif // _KERNEL_NT_REG_H diff --git a/kernel/main.c b/kernel/main.c index a97bef6..d46d52b 100644 --- a/kernel/main.c +++ b/kernel/main.c @@ -8,6 +8,7 @@ #include #include #include +#include #include #include #include @@ -241,6 +242,7 @@ static void subos_modular_core_boot(void) { metrics_init(); cron_init(); ob_init(); + reg_init(); service_manager_init(); printk(ANSI_BRIGHT_GREEN "OK\n" ANSI_RESET); diff --git a/kernel/nt/reg.c b/kernel/nt/reg.c new file mode 100644 index 0000000..a31ae40 --- /dev/null +++ b/kernel/nt/reg.c @@ -0,0 +1,260 @@ +// ----------------------------------------------------------------------------- +// SUB-OS NT-Inspired Configuration Registry (Cm) +// +// A key tree rooted at "\Registry" with typed values. Paths are backslash +// separated and begin at the root key named "Registry". Intermediate keys are +// created on demand by reg_create_key(); reg_open_key() only navigates. +// ----------------------------------------------------------------------------- + +#include +#include +#include +#include +#include +#include +#include +#include + +static reg_key_t* g_reg_root = NULL; +static uint32_t g_key_count = 0; +static uint32_t g_value_count = 0; +static spinlock_t g_reg_lock = SPINLOCK_INIT; + +const char* reg_type_name(reg_type_t t) { + switch (t) { + case REG_SZ: return "REG_SZ"; + case REG_BINARY: return "REG_BINARY"; + case REG_DWORD: return "REG_DWORD"; + case REG_MULTI_SZ: return "REG_MULTI_SZ"; + case REG_QWORD: return "REG_QWORD"; + default: return "REG_NONE"; + } +} + +reg_key_t* reg_root(void) { return g_reg_root; } + +// Caller holds g_reg_lock. +static reg_key_t* key_alloc(const char* name, reg_key_t* parent) { + reg_key_t* k = (reg_key_t*)kzalloc(sizeof(reg_key_t)); + if (!k) return NULL; + strncpy(k->name, name ? name : "", REG_NAME_MAX - 1); + k->parent = parent; + g_key_count++; + return k; +} + +// Caller holds g_reg_lock. +static reg_key_t* key_find_child(reg_key_t* key, const char* name) { + if (!key) return NULL; + for (int i = 0; i < key->child_count; i++) { + if (strcmp(key->children[i]->name, name) == 0) return key->children[i]; + } + return NULL; +} + +// Split a path and walk it, optionally creating missing keys. The first path +// component must be "Registry" (the root key's name). +static reg_key_t* walk_path(const char* path, bool create) { + if (!path || !g_reg_root) return NULL; + + char buf[192]; + strncpy(buf, path, sizeof(buf) - 1); + buf[sizeof(buf) - 1] = '\0'; + + reg_key_t* cur = g_reg_root; + char* p = buf; + if (*p == '\\') p++; + + // First component names the root itself. + char* first = p; + while (*p && *p != '\\') p++; + if (*p == '\\') { *p = '\0'; p++; } + if (strcmp(first, g_reg_root->name) != 0) { + return NULL; // path is not anchored at "\Registry" + } + + while (*p) { + char* seg = p; + while (*p && *p != '\\') p++; + if (*p == '\\') { *p = '\0'; p++; } + if (!*seg) continue; + + reg_key_t* child = key_find_child(cur, seg); + if (!child) { + if (!create) return NULL; + if (cur->child_count >= REG_MAX_CHILDREN) return NULL; + child = key_alloc(seg, cur); + if (!child) return NULL; + cur->children[cur->child_count++] = child; + } + cur = child; + } + return cur; +} + +reg_key_t* reg_create_key(const char* path) { + spin_lock(&g_reg_lock); + reg_key_t* k = walk_path(path, true); + spin_unlock(&g_reg_lock); + return k; +} + +reg_key_t* reg_open_key(const char* path) { + spin_lock(&g_reg_lock); + reg_key_t* k = walk_path(path, false); + spin_unlock(&g_reg_lock); + return k; +} + +// Caller holds g_reg_lock. +static reg_value_t* value_get_or_add(reg_key_t* key, const char* name) { + for (int i = 0; i < key->value_count; i++) { + if (strcmp(key->values[i]->name, name) == 0) return key->values[i]; + } + if (key->value_count >= REG_MAX_VALUES) return NULL; + reg_value_t* v = (reg_value_t*)kzalloc(sizeof(reg_value_t)); + if (!v) return NULL; + strncpy(v->name, name, REG_NAME_MAX - 1); + key->values[key->value_count++] = v; + g_value_count++; + return v; +} + +reg_value_t* reg_set_dword(reg_key_t* key, const char* name, uint32_t value) { + if (!key || !name) return NULL; + spin_lock(&g_reg_lock); + reg_value_t* v = value_get_or_add(key, name); + if (v) { + v->type = REG_DWORD; + v->data.dword = value; + v->data_len = sizeof(uint32_t); + } + spin_unlock(&g_reg_lock); + return v; +} + +reg_value_t* reg_set_qword(reg_key_t* key, const char* name, uint64_t value) { + if (!key || !name) return NULL; + spin_lock(&g_reg_lock); + reg_value_t* v = value_get_or_add(key, name); + if (v) { + v->type = REG_QWORD; + v->data.qword = value; + v->data_len = sizeof(uint64_t); + } + spin_unlock(&g_reg_lock); + return v; +} + +reg_value_t* reg_set_sz(reg_key_t* key, const char* name, const char* str) { + if (!key || !name) return NULL; + spin_lock(&g_reg_lock); + reg_value_t* v = value_get_or_add(key, name); + if (v) { + v->type = REG_SZ; + strncpy(v->data.str, str ? str : "", REG_STR_MAX - 1); + v->data.str[REG_STR_MAX - 1] = '\0'; + v->data_len = (uint32_t)strlen(v->data.str) + 1; + } + spin_unlock(&g_reg_lock); + return v; +} + +reg_value_t* reg_query_value(reg_key_t* key, const char* name) { + if (!key || !name) return NULL; + spin_lock(&g_reg_lock); + reg_value_t* found = NULL; + for (int i = 0; i < key->value_count; i++) { + if (strcmp(key->values[i]->name, name) == 0) { found = key->values[i]; break; } + } + spin_unlock(&g_reg_lock); + return found; +} + +int reg_enum_key_count(reg_key_t* key) { return key ? key->child_count : 0; } +reg_key_t* reg_enum_key(reg_key_t* key, int index) { + if (!key || index < 0 || index >= key->child_count) return NULL; + return key->children[index]; +} +int reg_enum_value_count(reg_key_t* key) { return key ? key->value_count : 0; } +reg_value_t* reg_enum_value(reg_key_t* key, int index) { + if (!key || index < 0 || index >= key->value_count) return NULL; + return key->values[index]; +} + +void reg_value_to_string(const reg_value_t* v, char* out, size_t out_len) { + if (!v || !out || out_len == 0) return; + switch (v->type) { + case REG_SZ: + case REG_MULTI_SZ: + snprintf(out, out_len, "%s", v->data.str); + break; + case REG_DWORD: + snprintf(out, out_len, "0x%08X (%u)", v->data.dword, v->data.dword); + break; + case REG_QWORD: + snprintf(out, out_len, "0x%016llX", (unsigned long long)v->data.qword); + break; + case REG_BINARY: + snprintf(out, out_len, "<%u bytes>", v->data_len); + break; + default: + snprintf(out, out_len, "(none)"); + break; + } +} + +void reg_get_stats(uint32_t* out_keys, uint32_t* out_values) { + if (out_keys) *out_keys = g_key_count; + if (out_values) *out_values = g_value_count; +} + +void reg_init(void) { + g_key_count = 0; + g_value_count = 0; + + g_reg_root = key_alloc("Registry", NULL); + if (!g_reg_root) { + printk(KERN_ERR "REG: failed to allocate the registry root\n"); + return; + } + + // Standard hives. + reg_create_key("\\Registry\\Machine"); + reg_create_key("\\Registry\\User"); + + // Hardware identification. + reg_key_t* sys = reg_create_key("\\Registry\\Machine\\Hardware\\Description\\System"); + if (sys) { + reg_set_sz(sys, "Identifier", "SUB-OS Modular Monolithic Kernel"); + reg_set_sz(sys, "SystemBootDevice", "\\Device\\Harddisk0\\Partition1"); + } + + // Control set. + reg_key_t* ctl = reg_create_key("\\Registry\\Machine\\System\\CurrentControlSet\\Control"); + if (ctl) { + reg_set_sz(ctl, "SystemStartOptions", "NOEXECUTE=OPTIN QUIET"); + reg_set_dword(ctl, "PreemptiveScheduler", 1); + } + + // SUB-OS software hive. + reg_key_t* sw = reg_create_key("\\Registry\\Machine\\Software\\SUB-OS"); + if (sw) { + reg_set_sz(sw, "Version", kernel_get_version()); + reg_set_sz(sw, "BuildArch", arch_get_name()); + reg_set_dword(sw, "GrowableHeap", 1); + reg_set_dword(sw, "ObjectManager", 1); + reg_set_qword(sw, "InstallCookie", 0x5355424F53000001ULL); + } + + // Default user profile. + reg_key_t* du = reg_create_key("\\Registry\\User\\.DEFAULT\\Environment"); + if (du) { + reg_set_sz(du, "PATH", "/bin:/sbin:/usr/bin"); + reg_set_sz(du, "SHELL", "/bin/sh"); + } + + printk(ANSI_BRIGHT_GREEN "REG: " ANSI_RESET + "NT Configuration Registry online (%u keys, %u values under \\Registry)\n", + g_key_count, g_value_count); +} diff --git a/userland/lazybox/lazybox.c b/userland/lazybox/lazybox.c index 2556da5..1f87d52 100644 --- a/userland/lazybox/lazybox.c +++ b/userland/lazybox/lazybox.c @@ -30,6 +30,7 @@ #include #include #include +#include #include #include #include @@ -587,6 +588,77 @@ static int applet_ntobj(int argc, char** argv) { return 0; } +static int reg_query_cmd(const char* path) { + reg_key_t* key = reg_open_key(path); + if (!key) { + printk(KERN_ERR "reg: key '%s' not found\n", path); + return 1; + } + printk(ANSI_BRIGHT_CYAN "%s\n" ANSI_RESET, path); + + int vc = reg_enum_value_count(key); + if (vc == 0) { + printk(" (no values)\n"); + } + for (int i = 0; i < vc; i++) { + reg_value_t* v = reg_enum_value(key, i); + char rendered[REG_STR_MAX + 32]; + reg_value_to_string(v, rendered, sizeof(rendered)); + printk(" " ANSI_YELLOW "%-20s" ANSI_RESET " %-11s %s\n", + v->name, reg_type_name(v->type), rendered); + } + + int kc = reg_enum_key_count(key); + for (int i = 0; i < kc; i++) { + reg_key_t* sub = reg_enum_key(key, i); + printk(ANSI_BRIGHT_GREEN " [%s]\n" ANSI_RESET, sub->name); + } + return 0; +} + +static int applet_reg(int argc, char** argv) { + if (argc < 2) { + printk("Usage: reg query <\\path>\n"); + printk(" reg add <\\path>\n"); + printk(" reg set <\\path> \n"); + printk(" reg stats\n"); + return 1; + } + + if (strcmp(argv[1], "stats") == 0) { + uint32_t keys = 0, values = 0; + reg_get_stats(&keys, &values); + printk("Registry: %u keys, %u values under \\Registry\n", keys, values); + return 0; + } + if (strcmp(argv[1], "query") == 0) { + return reg_query_cmd((argc >= 3) ? argv[2] : "\\Registry"); + } + if (strcmp(argv[1], "add") == 0 && argc >= 3) { + reg_key_t* k = reg_create_key(argv[2]); + if (!k) { printk(KERN_ERR "reg: failed to create '%s'\n", argv[2]); return 1; } + printk("Created key %s\n", argv[2]); + return 0; + } + if (strcmp(argv[1], "set") == 0 && argc >= 6) { + reg_key_t* k = reg_open_key(argv[2]); + if (!k) { printk(KERN_ERR "reg: key '%s' not found\n", argv[2]); return 1; } + if (strcmp(argv[4], "dword") == 0) { + reg_set_dword(k, argv[3], (uint32_t)atoi(argv[5])); + } else if (strcmp(argv[4], "sz") == 0) { + reg_set_sz(k, argv[3], argv[5]); + } else { + printk("reg: unknown type '%s' (use dword or sz)\n", argv[4]); + return 1; + } + printk("Set %s\\%s\n", argv[2], argv[3]); + return 0; + } + + printk("reg: unrecognised sub-command '%s'\n", argv[1]); + return 1; +} + static int applet_rand(int argc, char** argv) { int count = (argc >= 2) ? atoi(argv[1]) : 4; printk("Random Integers: "); @@ -2298,6 +2370,7 @@ static const lazybox_applet_t applets[] = { {"rle", applet_rle, "rle ", "Rust RLE compress round-trip","Crypto"}, {"objdir", applet_objdir, "objdir [\\path]", "List the NT object namespace","Kernel"}, {"ntobj", applet_ntobj, "ntobj", "NT Object Manager stats/demo","Kernel"}, + {"reg", applet_reg, "reg query|add|set|stats", "NT configuration registry", "Kernel"}, {"rand", applet_rand, "rand [count]", "Generate random numbers", "Crypto"}, {"certcheck", applet_certcheck, "certcheck", "View X.509 kernel keyring", "Security"}, {"capsh", applet_capsh, "capsh", "View process capabilities", "Security"},