--- /dev/null
+// SPDX-License-Identifier: BSD-3-Clause
+/*
+ * All rights reserved.
+ * See LICENSE for details.
+ *
+ * Source: https://github.com/tadryanom/AdrOS
+ */
+
+#ifndef UTMP_H
+#define UTMP_H
+
+#include <stdint.h>
+
+/* utmp record types (Linux-compatible) */
+#define EMPTY 0
+#define RUN_LVL 1
+#define BOOT_TIME 2
+#define NEW_TIME 3
+#define OLD_TIME 4
+#define INIT_PROCESS 5
+#define LOGIN_PROCESS 6
+#define USER_PROCESS 7
+#define DEAD_PROCESS 8
+
+/* utmp structure (simplified, Linux-compatible subset) */
+struct utmp {
+ short ut_type; /* Type of record */
+ uint32_t ut_pid; /* PID of login process */
+ char ut_line[32]; /* Device name of tty - "/dev/" */
+ char ut_id[4]; /* Terminal name suffix, or inittab(5) ID */
+ char ut_user[32]; /* Username */
+ char ut_host[256]; /* Hostname for remote login */
+ struct exit_status {
+ short e_termination; /* Process termination status */
+ short e_exit; /* Process exit status */
+ } ut_exit; /* Exit status of a process marked as DEAD_PROCESS */
+ int32_t ut_session; /* Session ID (getsid(2)), used for windowing */
+ struct {
+ int32_t tv_sec; /* Seconds */
+ int32_t tv_usec; /* Microseconds */
+ } ut_tv; /* Time entry was made */
+ int32_t ut_addr_v6[4]; /* Internet address of remote host */
+ char __unused[20]; /* Reserved for future use */
+};
+
+/* Kernel API for utmp management */
+void utmp_init(void);
+int utmp_login(uint32_t pid, const char* line, const char* user, const char* host);
+int utmp_logout(uint32_t pid, const char* line);
+int utmp_dead(uint32_t pid, int exit_status);
+struct utmp* utmp_get_by_pid(uint32_t pid);
+
+#endif /* UTMP_H */
--- /dev/null
+// SPDX-License-Identifier: BSD-3-Clause
+/*
+ * All rights reserved.
+ * See LICENSE for details.
+ *
+ * Source: https://github.com/tadryanom/AdrOS
+ */
+
+#include "utmp.h"
+#include "heap.h"
+#include "spinlock.h"
+#include "string.h"
+#include "console.h"
+#include "process.h"
+#include "timer.h"
+
+#include <stddef.h>
+
+#define UTMP_MAX_ENTRIES 32 /* L2: Reduced to avoid BSS overflow */
+
+static struct utmp g_utmp[UTMP_MAX_ENTRIES];
+static spinlock_t g_utmp_lock = {0};
+
+void utmp_init(void) {
+ for (int i = 0; i < UTMP_MAX_ENTRIES; i++) {
+ memset(&g_utmp[i], 0, sizeof(g_utmp[i]));
+ g_utmp[i].ut_type = EMPTY;
+ }
+}
+
+static int utmp_get_time(struct utmp* u) {
+ /* L2: Get current time (simplified - use timer ticks for now) */
+ extern uint32_t get_tick_count(void);
+ uint32_t ticks = get_tick_count();
+ u->ut_tv.tv_sec = (int32_t)(ticks / TIMER_HZ); /* Convert ticks to seconds */
+ u->ut_tv.tv_usec = (int32_t)((ticks % TIMER_HZ) * (1000000 / TIMER_HZ));
+ return 0;
+}
+
+int utmp_login(uint32_t pid, const char* line, const char* user, const char* host) {
+ if (!line || !user) return -EINVAL;
+
+ uintptr_t irqf = spin_lock_irqsave(&g_utmp_lock);
+
+ /* Find empty slot */
+ int slot = -1;
+ for (int i = 0; i < UTMP_MAX_ENTRIES; i++) {
+ if (g_utmp[i].ut_type == EMPTY) {
+ slot = i;
+ break;
+ }
+ }
+ if (slot < 0) {
+ spin_unlock_irqrestore(&g_utmp_lock, irqf);
+ return -ENOSPC;
+ }
+
+ struct utmp* u = &g_utmp[slot];
+ memset(u, 0, sizeof(*u));
+
+ u->ut_type = USER_PROCESS;
+ u->ut_pid = pid;
+
+ /* Copy line (device name) */
+ strncpy(u->ut_line, line, sizeof(u->ut_line) - 1);
+ u->ut_line[sizeof(u->ut_line) - 1] = '\0';
+
+ /* Copy username */
+ strncpy(u->ut_user, user, sizeof(u->ut_user) - 1);
+ u->ut_user[sizeof(u->ut_user) - 1] = '\0';
+
+ /* Copy hostname if provided */
+ if (host) {
+ strncpy(u->ut_host, host, sizeof(u->ut_host) - 1);
+ u->ut_host[sizeof(u->ut_host) - 1] = '\0';
+ }
+
+ /* Set session ID (only if called from process context) */
+ u->ut_session = 0; /* L2: Simplified - no session tracking for now */
+
+ /* Set timestamp */
+ utmp_get_time(u);
+
+ spin_unlock_irqrestore(&g_utmp_lock, irqf);
+ return 0;
+}
+
+int utmp_logout(uint32_t pid, const char* line) {
+ (void)line; /* L2: Line parameter not used for logout by PID */
+ uintptr_t irqf = spin_lock_irqsave(&g_utmp_lock);
+
+ for (int i = 0; i < UTMP_MAX_ENTRIES; i++) {
+ if (g_utmp[i].ut_type == USER_PROCESS && g_utmp[i].ut_pid == pid) {
+ g_utmp[i].ut_type = DEAD_PROCESS;
+ utmp_get_time(&g_utmp[i]);
+ spin_unlock_irqrestore(&g_utmp_lock, irqf);
+ return 0;
+ }
+ }
+
+ spin_unlock_irqrestore(&g_utmp_lock, irqf);
+ return -ESRCH;
+}
+
+int utmp_dead(uint32_t pid, int exit_status) {
+ uintptr_t irqf = spin_lock_irqsave(&g_utmp_lock);
+
+ for (int i = 0; i < UTMP_MAX_ENTRIES; i++) {
+ if (g_utmp[i].ut_type == USER_PROCESS && g_utmp[i].ut_pid == pid) {
+ g_utmp[i].ut_type = DEAD_PROCESS;
+ g_utmp[i].ut_exit.e_exit = (short)(exit_status & 0xFF);
+ g_utmp[i].ut_exit.e_termination = (short)((exit_status >> 8) & 0xFF);
+ utmp_get_time(&g_utmp[i]);
+ spin_unlock_irqrestore(&g_utmp_lock, irqf);
+ return 0;
+ }
+ }
+
+ spin_unlock_irqrestore(&g_utmp_lock, irqf);
+ return -ESRCH;
+}
+
+struct utmp* utmp_get_by_pid(uint32_t pid) {
+ uintptr_t irqf = spin_lock_irqsave(&g_utmp_lock);
+
+ for (int i = 0; i < UTMP_MAX_ENTRIES; i++) {
+ if (g_utmp[i].ut_type == USER_PROCESS && g_utmp[i].ut_pid == pid) {
+ spin_unlock_irqrestore(&g_utmp_lock, irqf);
+ return &g_utmp[i];
+ }
+ }
+
+ spin_unlock_irqrestore(&g_utmp_lock, irqf);
+ return NULL;
+}