From 7d90ac7d3d4ab40dd8123b240049370a8de12032 Mon Sep 17 00:00:00 2001 From: Benji Dial Date: Mon, 29 Dec 2025 18:27:34 -0500 Subject: [PATCH] environment variables for processes --- include/calcite/syscalls.h | 11 +- include/kernel-public/process.h | 32 +++++ include/kernel-public/syscall-numbers.h | 3 +- src/kernel/entry.c | 3 +- src/kernel/process.c | 164 +++++++++++++++++++++++- src/kernel/process.h | 28 +++- src/kernel/utility.c | 10 ++ src/kernel/utility.h | 7 + src/user-apps/hello/hello.c | 33 ++++- src/user-apps/init/init.c | 11 +- src/user-libs/calcite/syscalls.c | 10 +- 11 files changed, 293 insertions(+), 19 deletions(-) create mode 100644 include/kernel-public/process.h diff --git a/include/calcite/syscalls.h b/include/calcite/syscalls.h index 703d531..2f0abc0 100644 --- a/include/calcite/syscalls.h +++ b/include/calcite/syscalls.h @@ -18,6 +18,7 @@ #pragma once #include +#include #include #include #include @@ -41,7 +42,7 @@ void *map_pages(uint64_t count); void sleep_ms(uint64_t ms_to_sleep); //1 on success, 0 on failure. -int start_elf(const char *path); +int start_elf(const char *path, const struct process_start_info *info); enum ipc_dgram_result ipc_create_dgram_receiver(const char *address, ipc_dgram_receiver_handle_t *handle_out); enum ipc_dgram_result ipc_create_dgram_sender(const char *address, ipc_dgram_sender_handle_t *handle_out); @@ -56,3 +57,11 @@ enum ipc_dgram_result ipc_send_dgram(ipc_dgram_sender_handle_t handle, const voi //f should not return void create_thread(void (*f)(uint64_t x), uint64_t x); + +//value_space must be positive at entry. +//looks up the environment variable with this key. if there is no such +//variable, sets value_space to 0. otherwise, sets value_space to the +//length of the value including null terminator (which will be positive), +//and if that is at most what value_space was set to at entry, also +//copies the value including null terminator to value_out. +void get_envvar(const char *key, char *value_out, int *value_space); diff --git a/include/kernel-public/process.h b/include/kernel-public/process.h new file mode 100644 index 0000000..320acea --- /dev/null +++ b/include/kernel-public/process.h @@ -0,0 +1,32 @@ +/* Calcite, include/kernel-public/process.h + * Copyright 2025 Benji Dial + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program. If not, see . + */ + +#pragma once + +struct process_start_info { + + //keys of envvars to copy to new process. + const char **forwared_envvars; + int forwared_envvar_count; + + //keys and values to set in new process. + //entry i has key set_envvars[i * 2] and value set_envvars[i * 2 + 1]. + //set_envvar_count is number of envvars, not twice that. + const char **set_envvars; + int set_envvar_count; + +}; diff --git a/include/kernel-public/syscall-numbers.h b/include/kernel-public/syscall-numbers.h index 7e667c3..1c1cc3b 100644 --- a/include/kernel-public/syscall-numbers.h +++ b/include/kernel-public/syscall-numbers.h @@ -32,5 +32,6 @@ enum { SYSCALL_IPC_CREATE_DGRAM_SENDER, SYSCALL_IPC_RECEIVE_DGRAM, SYSCALL_IPC_SEND_DGRAM, - SYSCALL_CREATE_THREAD + SYSCALL_CREATE_THREAD, + SYSCALL_GET_ENVVAR }; diff --git a/src/kernel/entry.c b/src/kernel/entry.c index 5a4e233..1b4ac20 100644 --- a/src/kernel/entry.c +++ b/src/kernel/entry.c @@ -271,6 +271,7 @@ static const char *cmdline_look_up(const char *key) { register_syscall(SYSCALL_IPC_RECEIVE_DGRAM, (void *)&syscall_ipc_receive_dgram); register_syscall(SYSCALL_IPC_SEND_DGRAM, (void *)&syscall_ipc_send_dgram); register_syscall(SYSCALL_CREATE_THREAD, (void *)&syscall_create_thread); + register_syscall(SYSCALL_GET_ENVVAR, (void *)&syscall_get_envvar); //probe for drives @@ -298,7 +299,7 @@ static const char *cmdline_look_up(const char *key) { init_scheduler(); - if (!start_elf("root://calcite/apps/init/init.elf")) + if (start_elf("root://calcite/apps/init/init.elf") == 0) panic("could not start init.elf") resume_next_continuation(); diff --git a/src/kernel/process.c b/src/kernel/process.c index 2a1dfb8..be704a0 100644 --- a/src/kernel/process.c +++ b/src/kernel/process.c @@ -26,6 +26,7 @@ #include "heap.h" #include "fs.h" +#include #include #include @@ -66,6 +67,7 @@ void create_process(struct process *process_out) { process_out->n_threads = 0; process_out->files = 0; process_out->ipc_dgram_handles = 0; + process_out->env_pairs = 0; } @@ -345,7 +347,8 @@ int load_elf( // all other registers zeroed extern uint8_t thread_start; -int start_elf(const char *uri) { +//returns 0 on failure +struct process *start_elf(const char *uri) { struct process *process = heap_alloc(sizeof(struct process)); create_process(process); @@ -376,7 +379,7 @@ int start_elf(const char *uri) { add_to_queue(&ready_continuations, &ci); - return 1; + return process; } @@ -399,14 +402,45 @@ void syscall_create_thread(void (*f)(uint64_t), uint64_t x) { } -int syscall_start_elf(const char *uri) { +int syscall_start_elf(const char *path, const struct process_start_info *info) { assert(running_thread != 0) - if (!is_mapped_readable_string(running_thread->process, uri)) + if (!is_mapped_readable_string(running_thread->process, path) || + !is_mapped_readable( + running_thread->process, info, sizeof(struct process_start_info)) || + !is_mapped_readable( + running_thread->process, + info->forwared_envvars, + info->forwared_envvar_count * sizeof(const char *)) || + !is_mapped_readable( + running_thread->process, + info->set_envvars, + 2 * info->set_envvar_count * sizeof(const char *))) syscall_illegal_args(); - return start_elf(uri); + for (int i = 0; i < info->forwared_envvar_count; ++i) + if (!is_mapped_readable_string(running_thread->process, info->forwared_envvars[i])) + syscall_illegal_args(); + + for (int i = 0; i < 2 * info->set_envvar_count; ++i) + if (!is_mapped_readable_string(running_thread->process, info->set_envvars[i])) + syscall_illegal_args(); + + struct process *process = start_elf(path); + if (process == 0) + return 0; + + for (int i = 0; i < info->forwared_envvar_count; ++i) { + struct process_env_pair *pair = get_envvar(running_thread->process, info->forwared_envvars[i]); + if (pair != 0) + set_envvar(process, info->forwared_envvars[i], pair->value); + } + + for (int i = 0; i < info->set_envvar_count; ++i) + set_envvar(process, info->set_envvars[2 * i], info->set_envvars[2 * i + 1]); + + return 1; } @@ -428,6 +462,16 @@ void destroy_process(struct process *process) { process->ipc_dgram_handles_buffer_size * sizeof(struct process_ipc_dgram_handle_info)); } + if (process->env_pairs) { + for (int i = 0; i < process->env_pairs_buffer_size; ++i) + if (process->env_pairs[i].key_length != 0) { + heap_dealloc(process->env_pairs[i].key, process->env_pairs[i].key_length + 1); + heap_dealloc(process->env_pairs[i].value, process->env_pairs[i].value_length + 1); + } + heap_dealloc( + process->env_pairs, process->env_pairs_buffer_size * sizeof(struct process_env_pair)); + } + for (int p3i = 0; p3i < 512; ++p3i) if (process->p3_virtual_base[p3i]) { for (int p2i = 0; p2i < 512; ++p2i) @@ -804,3 +848,113 @@ enum ipc_dgram_result syscall_ipc_send_dgram( return send_ipc_dgram(box, data, bytes); } + +#define INITIAL_ENVVAR_BUFFER_SIZE 32 + +void set_envvar(struct process *process, const char *key, const char *value) { + + if (!process->env_pairs) { + process->env_pairs = heap_alloc(INITIAL_ENVVAR_BUFFER_SIZE * sizeof(struct process_env_pair)); + process->env_pairs_buffer_size = INITIAL_ENVVAR_BUFFER_SIZE; + for (int i = 0; i < INITIAL_ENVVAR_BUFFER_SIZE; ++i) + process->env_pairs[i].key_length = 0; + } + + int key_length = 0; + while (key[key_length] != 0) + ++key_length; + + int value_length = 0; + while (value[value_length] != 0) + ++value_length; + + assert(key_length != 0) + assert(value_length != 0) + + for (int i = 0; i < process->env_pairs_buffer_size; ++i) { + if (process->env_pairs[i].key_length != key_length) + continue; + for (int j = 0; j < key_length; ++j) + if (process->env_pairs[i].key[j] != key[j]) + continue; + heap_dealloc(process->env_pairs[i].value, process->env_pairs[i].value_length + 1); + char *value_copy = heap_alloc(value_length + 1); + memcpy(value_copy, value, value_length + 1); + process->env_pairs[i].value = value_copy; + process->env_pairs[i].value_length = value_length; + return; + } + + struct process_env_pair *pair; + for (int i = 0; i < process->env_pairs_buffer_size; ++i) + if (process->env_pairs[i].key_length == 0) { + pair = &process->env_pairs[i]; + goto got_pair; + } + + double_buffer_zero( + (void **)&process->env_pairs, + &process->env_pairs_buffer_size, + sizeof(struct process_env_pair)); + + pair = &process->env_pairs[process->env_pairs_buffer_size / 2]; + +got_pair: + char *key_copy = heap_alloc(key_length + 1); + memcpy(key_copy, key, key_length + 1); + pair->key = key_copy; + pair->key_length = key_length; + char *value_copy = heap_alloc(value_length + 1); + memcpy(value_copy, value, value_length + 1); + pair->value = value_copy; + pair->value_length = value_length; + +} + +struct process_env_pair *get_envvar(struct process *process, const char *key) { + + int key_length = 0; + while (key[key_length] != 0) + ++key_length; + + assert(key_length > 0) + + for (int i = 0; i < process->env_pairs_buffer_size; ++i) { + if (process->env_pairs[i].key_length != key_length) + continue; + for (int j = 0; j < key_length; ++j) + if (process->env_pairs[i].key[j] != key[j]) + continue; + return &process->env_pairs[i]; + } + + return 0; + +} + +void syscall_get_envvar(const char *key, char *value_out, int *value_space) { + + assert(running_thread != 0) + + if (!is_mapped_readable_string(running_thread->process, key) || + !is_mapped_writable(running_thread->process, value_space, sizeof(int)) || + *value_space <= 0 || + !is_mapped_writable(running_thread->process, value_out, *value_space)) + syscall_illegal_args(); + + struct process_env_pair *pair = get_envvar(running_thread->process, key); + + if (pair == 0) { + *value_space = 0; + return; + } + + if (*value_space < pair->value_length + 1) { + *value_space = pair->value_length + 1; + return; + } + + *value_space = pair->value_length + 1; + memcpy(value_out, pair->value, pair->value_length + 1); + +} diff --git a/src/kernel/process.h b/src/kernel/process.h index 75a8151..1650a0d 100644 --- a/src/kernel/process.h +++ b/src/kernel/process.h @@ -20,6 +20,7 @@ #include "fs.h" #include +#include #include #include @@ -38,6 +39,16 @@ struct process_ipc_dgram_handle_info { int is_receiver; }; +//lengths don't include null terminators, buffers do. +//key and value should not be empty. +struct process_env_pair { + //0 for unused + int key_length; + int value_length; + char *key; + char *value; +}; + struct process { uint64_t p4_physical_base; @@ -55,6 +66,9 @@ struct process { struct process_ipc_dgram_handle_info *ipc_dgram_handles; int ipc_dgram_handles_buffer_size; + struct process_env_pair *env_pairs; + int env_pairs_buffer_size; + //0 for missing levels. just bottom p3 of address space. uint64_t *p2_virtual_bases[512]; uint64_t **p1_virtual_bases[512]; @@ -104,13 +118,13 @@ int load_elf( struct process *process, uint64_t *entry_out, const struct fs_info *fs_info, void *fs_node); -//returns 0 on failure, 1 on success. +//returns 0 on failure. //creates a process and a thread in that process, loads the elf into the process, //and schedules a ready task that sets the running thread to the new thread and //starts user mode at the elf's entry point. -int start_elf(const char *uri); +struct process *start_elf(const char *uri); -int syscall_start_elf(const char *uri); +int syscall_start_elf(const char *path, const struct process_start_info *info); void destroy_process(struct process *process); void destroy_thread(struct thread *thread); @@ -155,3 +169,11 @@ enum ipc_dgram_result syscall_ipc_send_dgram( //f should not return. void syscall_create_thread(void (*f)(uint64_t x), uint64_t x); + +void set_envvar(struct process *process, const char *key, const char *value); + +//0 if unset +struct process_env_pair *get_envvar(struct process *process, const char *key); + +//see comment in include/calcite/syscalls.h +void syscall_get_envvar(const char *key, char *value_out, int *value_space); diff --git a/src/kernel/utility.c b/src/kernel/utility.c index 944ede5..2058505 100644 --- a/src/kernel/utility.c +++ b/src/kernel/utility.c @@ -16,6 +16,7 @@ */ #include "utility.h" +#include "heap.h" int strequ(const char *str1, const char *str2) { while (1) { @@ -35,3 +36,12 @@ uint32_t end_swap_u32(uint32_t value) { (((value >> 8) & 0xff) << 16) | ((value & 0xff) << 24); } + +void double_buffer_zero(void **buffer, int *length, uint64_t bytes_per_entry) { + void *new_buffer = heap_alloc(2 * *length * bytes_per_entry); + memcpy(new_buffer, *buffer, *length * bytes_per_entry); + heap_dealloc(*buffer, *length * bytes_per_entry); + memzero(new_buffer + *length * bytes_per_entry, *length * bytes_per_entry); + *buffer = new_buffer; + *length *= 2; +} diff --git a/src/kernel/utility.h b/src/kernel/utility.h index 24b38e1..a7d99db 100644 --- a/src/kernel/utility.h +++ b/src/kernel/utility.h @@ -27,3 +27,10 @@ void memzero(void *start, uint64_t bytes); //swaps the endianness of the value uint32_t end_swap_u32(uint32_t value); + +//1. allocates a new buffer with 2 * length * bytes_per_entry bytes +//2. copies length * bytes_per_entry bytes from old buffer to new buffer +//3. deallocates old buffer +//4. zeroes rest of new buffer +//5. sets buffer and length to new buffer and twice length +void double_buffer_zero(void **buffer, int *length, uint64_t bytes_per_entry); diff --git a/src/user-apps/hello/hello.c b/src/user-apps/hello/hello.c index 63bd26c..9922874 100644 --- a/src/user-apps/hello/hello.c +++ b/src/user-apps/hello/hello.c @@ -15,7 +15,7 @@ * with this program. If not, see . */ -#include "kernel-public/ipc.h" +#include #include #include @@ -72,20 +72,43 @@ static void copy_frame() { end_thread(); } +static int strequ(const char *str1, const char *str2) { + while (1) { + if (*str1 == 0 && *str2 == 0) + return 1; + if (*str1 != *str2) + return 0; + ++str1; + ++str2; + } +} + +static int memequ(const char *str1, const char *str2, int length) { + for (int i = 0; i < length; ++i) + if (str1[i] != str2[i]) + return 0; + return 1; +} + void main() { map_framebuffer(&fb_info); create_image(fb_info.fb_width, fb_info.fb_height, &fbb); + char buffer[100]; + + int bytes = 100; + get_envvar("hello", buffer, &bytes); + if (bytes != 6 || !strequ(buffer, "world")) + panic(); + ipc_dgram_receiver_handle_t receiver; if (ipc_create_dgram_receiver("calcite-test", &receiver) != IPR_SUCCESS) panic(); - char buffer[5]; - int bytes = 5; + bytes = 100; if (ipc_receive_dgram(receiver, buffer, &bytes) != IPR_SUCCESS || - bytes != 5 || - buffer[0] != 'h' || buffer[1] != 'e' || buffer[2] != 'l' || buffer[3] != 'l' || buffer[4] != 'o') + bytes != 5 || !memequ(buffer, "hello", 5)) panic(); if (!load_pam("root://calcite/apps/hello/pointer.pam", &cursor_image)) diff --git a/src/user-apps/init/init.c b/src/user-apps/init/init.c index 7ac9f1b..33a8b1e 100644 --- a/src/user-apps/init/init.c +++ b/src/user-apps/init/init.c @@ -15,6 +15,7 @@ * with this program. If not, see . */ +#include #include #include #include @@ -76,7 +77,15 @@ void main() { continue; line[line_length] = 0; - start_elf(line); + + const char *set_envvars[2] = { "hello", "world" }; + + struct process_start_info psi; + psi.forwared_envvar_count = 0; + psi.set_envvar_count = 1; + psi.set_envvars = set_envvars; + + start_elf(line, &psi); } diff --git a/src/user-libs/calcite/syscalls.c b/src/user-libs/calcite/syscalls.c index 82ee61c..73c3c79 100644 --- a/src/user-libs/calcite/syscalls.c +++ b/src/user-libs/calcite/syscalls.c @@ -16,6 +16,7 @@ */ #include +#include #include #include #include @@ -66,8 +67,8 @@ void sleep_ms(uint64_t ms_to_sleep) { do_syscall(ms_to_sleep, 0, 0, SYSCALL_SLEEP_MS); } -int start_elf(const char *uri) { - return do_syscall((uint64_t)uri, 0, 0, SYSCALL_START_ELF); +int start_elf(const char *path, const struct process_start_info *info) { + return do_syscall((uint64_t)path, (uint64_t)info, 0, SYSCALL_START_ELF); } enum ipc_dgram_result ipc_create_dgram_receiver(const char *address, ipc_dgram_receiver_handle_t *handle_out) { @@ -92,3 +93,8 @@ enum ipc_dgram_result ipc_send_dgram(ipc_dgram_sender_handle_t handle, const voi void create_thread(void (*f)(uint64_t x), uint64_t x) { do_syscall((uint64_t)f, x, 0, SYSCALL_CREATE_THREAD); } + +void get_envvar(const char *key, char *value_out, int *value_space) { + do_syscall( + (uint64_t)key, (uint64_t)value_out, (uint64_t)value_space, SYSCALL_GET_ENVVAR); +}