diff --git a/apps/env-test/env-test.S b/apps/env-test/env-test.S new file mode 100644 index 0000000..c417b95 --- /dev/null +++ b/apps/env-test/env-test.S @@ -0,0 +1,123 @@ +# +# env-test: Tests environment variable syscalls. +# +# 1. Gets CWD (should be "/") +# 2. Sets TEST=hello +# 3. Gets TEST (should be "hello") +# 4. Forks - child gets TEST, verifies it was copied +# 5. Both print results and exit +# + +.section .text +.global _start + +.equ SYS_EXIT, 0 +.equ SYS_WRITE, 1 +.equ SYS_FORK, 3 +.equ SYS_WAITPID, 6 +.equ SYS_GETENV, 8 +.equ SYS_SETENV, 9 + +# Helper: write a string (addr in %ecx, len in %edx) to stdout +.macro WRITE_STR addr, len + movl $SYS_WRITE, %eax + movl $1, %ebx + movl \addr, %ecx + movl \len, %edx + int $0x80 +.endm + +_start: + # 1. Get CWD env var + movl $SYS_GETENV, %eax + movl $key_cwd, %ebx + movl $buf, %ecx + movl $128, %edx + int $0x80 + + # Print "CWD=" + WRITE_STR $label_cwd, $4 + # Print the value (length returned in eax) + movl %eax, %edx # length + movl $SYS_WRITE, %eax + movl $1, %ebx + movl $buf, %ecx + int $0x80 + WRITE_STR $newline, $1 + + # 2. Set TEST=hello + movl $SYS_SETENV, %eax + movl $key_test, %ebx + movl $val_hello, %ecx + int $0x80 + + # 3. Get TEST + movl $SYS_GETENV, %eax + movl $key_test, %ebx + movl $buf, %ecx + movl $128, %edx + int $0x80 + + # Print "TEST=" + WRITE_STR $label_test, $5 + movl %eax, %edx + movl $SYS_WRITE, %eax + movl $1, %ebx + movl $buf, %ecx + int $0x80 + WRITE_STR $newline, $1 + + # 4. Fork + movl $SYS_FORK, %eax + int $0x80 + testl %eax, %eax + jz .child + +.parent: + pushl %eax # save child PID + WRITE_STR $msg_parent, $15 + + # Wait for child + popl %ebx + movl $SYS_WAITPID, %eax + int $0x80 + + # Exit + movl $SYS_EXIT, %eax + movl $0, %ebx + int $0x80 + +.child: + # Child: get TEST to verify it was inherited + movl $SYS_GETENV, %eax + movl $key_test, %ebx + movl $buf, %ecx + movl $128, %edx + int $0x80 + + # Print "Child TEST=" + WRITE_STR $label_child_test, $11 + movl %eax, %edx + movl $SYS_WRITE, %eax + movl $1, %ebx + movl $buf, %ecx + int $0x80 + WRITE_STR $newline, $1 + + # Exit + movl $SYS_EXIT, %eax + movl $0, %ebx + int $0x80 + +.section .rodata +key_cwd: .asciz "CWD" +key_test: .asciz "TEST" +val_hello: .asciz "hello" +label_cwd: .ascii "CWD=" +label_test: .ascii "TEST=" +label_child_test: .ascii "Child TEST=" +msg_parent: .ascii "Parent done!\n\0\0" +newline: .ascii "\n" + +.section .bss +buf: .space 128 diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 5b508fa..0da459d 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -19,6 +19,7 @@ add_executable(kernel cpio.c vfs.c initrd_fs.c + env.c interrupts.S kernel.c ) diff --git a/src/env.c b/src/env.c new file mode 100644 index 0000000..8e28ba3 --- /dev/null +++ b/src/env.c @@ -0,0 +1,104 @@ +/** + * @file env.c + * @brief Per-process environment variable implementation. + * + * Provides a simple key=value store per process. Each process has a fixed + * array of ENV_MAX_VARS entries. Empty key strings indicate unused slots. + */ + +#include "env.h" +#include + +void env_init(env_block_t *env) { + memset(env, 0, sizeof(env_block_t)); +} + +/** + * Find the slot index for a given key. + * + * @param env Pointer to the environment block. + * @param key Variable name to search for. + * @return Slot index, or -1 if not found. + */ +static int env_find(const env_block_t *env, const char *key) { + for (int i = 0; i < ENV_MAX_VARS; i++) { + if (env->vars[i].key[0] != '\0' && + strcmp(env->vars[i].key, key) == 0) { + return i; + } + } + return -1; +} + +/** + * Find an empty slot in the environment block. + * + * @param env Pointer to the environment block. + * @return Slot index, or -1 if full. + */ +static int env_find_free(const env_block_t *env) { + for (int i = 0; i < ENV_MAX_VARS; i++) { + if (env->vars[i].key[0] == '\0') { + return i; + } + } + return -1; +} + +int32_t env_get(const env_block_t *env, const char *key, char *buf, uint32_t bufsize) { + int idx = env_find(env, key); + if (idx < 0) { + return -1; + } + + uint32_t vlen = strlen(env->vars[idx].value); + if (buf && bufsize > 0) { + uint32_t copy_len = vlen; + if (copy_len >= bufsize) { + copy_len = bufsize - 1; + } + memcpy(buf, env->vars[idx].value, copy_len); + buf[copy_len] = '\0'; + } + return (int32_t)vlen; +} + +int32_t env_set(env_block_t *env, const char *key, const char *value) { + if (!key || key[0] == '\0') { + return -1; + } + + /* If value is NULL or empty, unset the variable */ + if (!value || value[0] == '\0') { + int idx = env_find(env, key); + if (idx >= 0) { + env->vars[idx].key[0] = '\0'; + env->vars[idx].value[0] = '\0'; + } + return 0; + } + + /* Check if key already exists */ + int idx = env_find(env, key); + if (idx < 0) { + /* Need a new slot */ + idx = env_find_free(env); + if (idx < 0) { + return -1; /* Environment full */ + } + } + + /* Copy key */ + strncpy(env->vars[idx].key, key, ENV_MAX_KEY - 1); + env->vars[idx].key[ENV_MAX_KEY - 1] = '\0'; + + /* Copy value */ + strncpy(env->vars[idx].value, value, ENV_MAX_VALUE - 1); + env->vars[idx].value[ENV_MAX_VALUE - 1] = '\0'; + + return 0; +} + +void env_copy(env_block_t *dst, const env_block_t *src) { + memcpy(dst, src, sizeof(env_block_t)); +} diff --git a/src/env.h b/src/env.h new file mode 100644 index 0000000..7ffea98 --- /dev/null +++ b/src/env.h @@ -0,0 +1,75 @@ +/** + * @file env.h + * @brief Per-process environment variable support. + * + * Each process has a fixed-size environment table storing key=value pairs. + * Environment variables are copied to child processes during fork(). + * User-mode programs interact with the environment via SYS_GETENV and + * SYS_SETENV system calls. + */ + +#ifndef ENV_H +#define ENV_H + +#include + +/** Maximum number of environment variables per process. */ +#define ENV_MAX_VARS 32 +/** Maximum length of an environment variable key (including NUL). */ +#define ENV_MAX_KEY 64 +/** Maximum length of an environment variable value (including NUL). */ +#define ENV_MAX_VALUE 128 + +/** + * A single environment variable entry. + */ +typedef struct { + char key[ENV_MAX_KEY]; /**< Variable name (empty string = unused). */ + char value[ENV_MAX_VALUE]; /**< Variable value. */ +} env_var_t; + +/** + * Environment block for a process. + */ +typedef struct { + env_var_t vars[ENV_MAX_VARS]; +} env_block_t; + +/** + * Initialize an environment block (all entries empty). + * + * @param env Pointer to the environment block to initialize. + */ +void env_init(env_block_t *env); + +/** + * Get an environment variable. + * + * @param env Pointer to the environment block. + * @param key Variable name to look up. + * @param buf Buffer to write the value into. + * @param bufsize Size of the buffer. + * @return Length of the value (excluding NUL), or -1 if not found. + * If bufsize is too small, the value is truncated. + */ +int32_t env_get(const env_block_t *env, const char *key, char *buf, uint32_t bufsize); + +/** + * Set an environment variable. Creates it if it doesn't exist. + * + * @param env Pointer to the environment block. + * @param key Variable name (must not be empty). + * @param value Variable value (NULL or empty string to unset). + * @return 0 on success, -1 if the environment is full. + */ +int32_t env_set(env_block_t *env, const char *key, const char *value); + +/** + * Copy an environment block. + * + * @param dst Destination environment block. + * @param src Source environment block. + */ +void env_copy(env_block_t *dst, const env_block_t *src); + +#endif /* ENV_H */ diff --git a/src/kernel.c b/src/kernel.c index 7acb47b..45f831c 100644 --- a/src/kernel.c +++ b/src/kernel.c @@ -152,18 +152,21 @@ void kernel_main(uint32_t magic, uint32_t addr) { offset_print("FAILED to kmalloc\n"); } - /* Load hello-world from the initrd and run it as a user process */ + /* Load the initial program from the initrd and run it */ cpio_entry_t app_entry; - if (cpio_find("hello-world", &app_entry) == 0) { - offset_print("Found hello-world in initrd ("); + const char *init_app = "sh"; + if (cpio_find(init_app, &app_entry) == 0) { + offset_print("Found "); + offset_print(init_app); + offset_print(" in initrd ("); print_hex(app_entry.datasize); offset_print(" bytes)\n"); - int32_t pid = process_create("hello-world", + int32_t pid = process_create(init_app, app_entry.data, app_entry.datasize); if (pid > 0) { - offset_print("Created hello-world process, pid="); + offset_print("Created init process, pid="); print_hex((uint32_t)pid); /* Enable interrupts before entering user mode */ @@ -173,10 +176,11 @@ void kernel_main(uint32_t magic, uint32_t addr) { /* Enter user mode - does not return */ process_run_first(); } else { - offset_print("FAILED to create hello-world process\n"); + offset_print("FAILED to create init process\n"); } } else { - offset_print("hello-world not found in initrd\n"); + offset_print(init_app); + offset_print(" not found in initrd\n"); } /* Enable interrupts */ diff --git a/src/process.c b/src/process.c index af9ebf5..b0b83d1 100644 --- a/src/process.c +++ b/src/process.c @@ -71,6 +71,11 @@ int32_t process_create(const char *name, const void *code, uint32_t size) { memcpy(proc->name, name, nlen); proc->name[nlen] = '\0'; + /* Initialize environment and working directory */ + env_init(&proc->env); + strcpy(proc->cwd, "/"); + env_set(&proc->env, "CWD", "/"); + /* Allocate kernel stack (full page, not from kmalloc which has header overhead) */ void *kstack = paging_alloc_page(); if (!kstack) { diff --git a/src/process.h b/src/process.h index ddd2090..a26f78d 100644 --- a/src/process.h +++ b/src/process.h @@ -12,6 +12,7 @@ #include #include #include "isr.h" +#include "env.h" /** Maximum number of concurrent processes. */ #define MAX_PROCESSES 64 @@ -58,6 +59,8 @@ typedef struct process { uint32_t parent_pid; /**< Parent process ID. */ uint32_t waiting_for_pid; /**< PID we are blocked waiting for (if BLOCKED). */ char name[32]; /**< Process name (for debugging). */ + char cwd[128]; /**< Current working directory. */ + env_block_t env; /**< Per-process environment variables. */ } process_t; /** diff --git a/src/syscall.c b/src/syscall.c index 058d7cc..4c799ad 100644 --- a/src/syscall.c +++ b/src/syscall.c @@ -9,6 +9,7 @@ #include "syscall.h" #include "process.h" +#include "env.h" #include "port_io.h" #include "vga.h" #include @@ -139,6 +140,37 @@ static int32_t sys_exec(registers_t *regs) { return -1; /* Not implemented yet */ } +/** + * Handle SYS_GETENV: get an environment variable. + * EBX = key pointer, ECX = value buffer pointer, EDX = buffer size. + * Returns length of value, or -1 if not found. + */ +static int32_t sys_getenv(registers_t *regs) { + const char *key = (const char *)regs->ebx; + char *buf = (char *)regs->ecx; + uint32_t bufsize = regs->edx; + + process_t *cur = process_current(); + if (!cur) return -1; + + return env_get(&cur->env, key, buf, bufsize); +} + +/** + * Handle SYS_SETENV: set an environment variable. + * EBX = key pointer, ECX = value pointer (NULL to unset). + * Returns 0 on success, -1 on error. + */ +static int32_t sys_setenv(registers_t *regs) { + const char *key = (const char *)regs->ebx; + const char *value = (const char *)regs->ecx; + + process_t *cur = process_current(); + if (!cur) return -1; + + return env_set(&cur->env, key, value); +} + /** System call dispatch table. */ typedef int32_t (*syscall_fn)(registers_t *); static syscall_fn syscall_table[NUM_SYSCALLS] = { @@ -150,6 +182,8 @@ static syscall_fn syscall_table[NUM_SYSCALLS] = { [SYS_YIELD] = sys_yield, [SYS_WAITPID] = sys_waitpid, [SYS_EXEC] = sys_exec, + [SYS_GETENV] = sys_getenv, + [SYS_SETENV] = sys_setenv, }; void syscall_handler(registers_t *regs) { diff --git a/src/syscall.h b/src/syscall.h index e0344c2..02e4df3 100644 --- a/src/syscall.h +++ b/src/syscall.h @@ -22,9 +22,11 @@ #define SYS_YIELD 5 /**< Yield the CPU. */ #define SYS_WAITPID 6 /**< Wait for a child process. pid=EBX. */ #define SYS_EXEC 7 /**< Execute a program. path=EBX, argv=ECX. */ +#define SYS_GETENV 8 /**< Get environment variable. key=EBX, buf=ECX, bufsize=EDX. */ +#define SYS_SETENV 9 /**< Set environment variable. key=EBX, value=ECX. */ /** Total number of system calls. */ -#define NUM_SYSCALLS 8 +#define NUM_SYSCALLS 10 /** * Initialize the system call handler.