diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..786e1b6 --- /dev/null +++ b/Makefile @@ -0,0 +1,61 @@ +CC = gcc +CFLAGS = -O2 -g -Wall -Wextra -I./include -ffreestanding -nostdlib -fno-builtin +LDFLAGS = -nostdlib + +KERNEL_OBJS = boot/boot.o \ + boot/paging.o \ + boot/context.o \ + kernel/kernel.o \ + system/init.o \ + system/memory.o \ + system/paging.o \ + system/process.o \ + system/filesystem.o \ + system/fs_utils.o \ + system/file_io.o \ + system/package.o \ + device/video.o \ + device/keyboard.o \ + shell/shell.o \ + shell/shell_io.o \ + shell/fs_commands.o \ + shell/pkg_commands.o + +C_OBJS = kernel/kernel.o \ + system/init.o \ + system/memory.o \ + system/paging.o \ + system/process.o \ + system/filesystem.o \ + system/fs_utils.o \ + system/file_io.o \ + system/package.o \ + device/video.o \ + device/keyboard.o \ + shell/shell.o \ + shell/shell_io.o \ + shell/fs_commands.o \ + shell/pkg_commands.o \ + lib/string.o + +.PHONY: all clean test + +all: amethystos.bin + +amethystos.bin: $(KERNEL_OBJS) + $(LD) $(LDFLAGS) -o $@ $^ + +%.o: %.c + $(CC) $(CFLAGS) -c $< -o $@ + +%.o: %.s + $(AS) $< -o $@ + +%.o: %.asm + nasm -f elf32 $< -o $@ + +clean: + rm -f $(KERNEL_OBJS) amethystos.bin + +test: $(C_OBJS) + @echo "C files compiled successfully" diff --git a/README.md b/README.md new file mode 100644 index 0000000..ddd4d28 --- /dev/null +++ b/README.md @@ -0,0 +1,94 @@ +# AmethystOS + +AmethystOS is an educational operating system based on XINU architecture with an amethyst theme. This project serves as a learning platform for operating system concepts and implementation. + +## Features + +### Core System +- Custom bootloader with multiboot support +- Memory management with paging +- Process scheduling and context switching +- Interactive shell with command history +- Amethyst-themed UI elements + +### Device Drivers +- VGA text mode driver with color support +- PS/2 keyboard driver with full layout support +- LED control (Caps Lock, Num Lock, Scroll Lock) +- Support for extended keyboard keys + +### File System +- Hierarchical directory structure +- Basic file operations (create, read, write, delete) +- Directory operations (mkdir, rmdir, list) +- Path manipulation (absolute/relative paths) +- File permissions (read/write/execute) +- File descriptors for I/O operations + +### Package Manager (amy) +The `amy` package manager provides basic software management: +- Package installation and removal +- Dependency management +- Package database +- Search functionality +- Package information display + +Commands: +``` +amy install - Install a package +amy remove - Remove a package +amy update [package] - Update packages +amy list - List installed packages +amy search - Search for packages +amy info - Show package information +``` + +### Shell Commands +- File Operations: ls, cd, pwd, mkdir, rmdir, touch, rm +- Process Management: ps, kill +- System Information: meminfo +- UI Customization: clear, color +- Package Management: amy + +## Project Structure + +- `boot/` - Bootloader and low-level initialization +- `kernel/` - Core kernel components +- `system/` - System services (memory, process, filesystem) +- `device/` - Device drivers +- `include/` - Header files +- `lib/` - Library functions +- `shell/` - Shell implementation +- `doc/` - Documentation + +## Building + +1. Prerequisites: + - GCC cross-compiler (i686-elf target) + - GNU Make + - NASM assembler + - GNU Binutils + +2. Build Commands: + ```sh + make # Build the operating system + make clean # Clean build files + ``` + +## Development + +The system is designed to be educational and extensible. Key components are modular and well-documented. The amethyst theme is consistently applied throughout the user interface. + +### Adding New Features +1. Device Drivers: Add new files in `device/` +2. System Services: Implement in `system/` +3. Shell Commands: Add to `shell/` +4. Packages: Register in the package database + +## License + +MIT License + +Copyright (c) 2025 AmethystOS + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files. diff --git a/boot.asm b/boot.asm new file mode 100644 index 0000000..b6b485b --- /dev/null +++ b/boot.asm @@ -0,0 +1,37 @@ +; Multiboot header for GRUB +MBALIGN equ 1 << 0 +MEMINFO equ 1 << 1 +FLAGS equ MBALIGN | MEMINFO +MAGIC equ 0x1BADB002 +CHECKSUM equ -(MAGIC + FLAGS) + +section .multiboot +align 4 + dd MAGIC + dd FLAGS + dd CHECKSUM + +section .bss +align 16 +stack_bottom: + resb 16384 ; 16 KiB +stack_top: + +section .text +global _start +_start: + mov esp, stack_top ; Set up stack + + ; Initialize processor state + cli ; Disable interrupts + cld ; Clear direction flag + + ; Call the kernel + extern kmain + call kmain + + ; If kernel returns, enter infinite loop + cli +.hang: + hlt + jmp .hang diff --git a/boot.o b/boot.o new file mode 100644 index 0000000..294fd3b Binary files /dev/null and b/boot.o differ diff --git a/context.asm b/context.asm new file mode 100644 index 0000000..528fa76 --- /dev/null +++ b/context.asm @@ -0,0 +1,64 @@ +; Context switching functionality +global context_switch + +section .text + +context_switch: + ; Save current context if it exists + ; old_context is in [esp + 4] + ; new_context is in [esp + 8] + + mov eax, [esp + 4] ; Get old_context + test eax, eax ; Check if NULL + jz load_new ; If NULL, just load new context + + ; Save current context + mov [eax + 0], ebx ; Save general purpose registers + mov [eax + 4], ecx + mov [eax + 8], edx + mov [eax + 12], esi ; Save index registers + mov [eax + 16], edi + mov [eax + 20], ebp ; Save base pointer + + pushf ; Get flags + pop ecx + mov [eax + 28], ecx ; Save flags + + mov ecx, cr3 + mov [eax + 32], ecx ; Save page directory base + + mov ecx, [esp] ; Get return address + mov [eax + 24], ecx ; Save as EIP + + lea ecx, [esp + 4] + mov [eax + 36], ecx ; Save ESP + +load_new: + ; Load new context + mov eax, [esp + 8] ; Get new_context + + ; Load new page directory if different + mov ecx, [eax + 32] ; Get new CR3 + mov edx, cr3 + cmp ecx, edx + je .skip_cr3 + mov cr3, ecx +.skip_cr3: + + ; Load registers + mov ebx, [eax + 0] ; Restore general purpose registers + mov ecx, [eax + 4] + mov edx, [eax + 8] + mov esi, [eax + 12] ; Restore index registers + mov edi, [eax + 16] + mov ebp, [eax + 20] ; Restore base pointer + + ; Load flags + push dword [eax + 28] + popf + + ; Set up new stack frame + mov esp, [eax + 36] ; Restore stack pointer + push dword [eax + 24] ; Push return address (EIP) + + ret ; Jump to new context diff --git a/context.o b/context.o new file mode 100644 index 0000000..47b7ff5 Binary files /dev/null and b/context.o differ diff --git a/file_io.c b/file_io.c new file mode 100644 index 0000000..8f92df9 --- /dev/null +++ b/file_io.c @@ -0,0 +1,239 @@ +#include +#include +#include +#include + +/* File descriptor table */ +#define MAX_OPEN_FILES 32 + +typedef struct { + uint32_t inode; + uint32_t offset; + uint8_t mode; + uint8_t is_used; +} file_descriptor_t; + +static file_descriptor_t fd_table[MAX_OPEN_FILES]; + +/* Initialize file I/O system */ +void init_file_io(void) { + memset(fd_table, 0, sizeof(fd_table)); +} + +/* Find free file descriptor */ +static int get_free_fd(void) { + for (int i = 0; i < MAX_OPEN_FILES; i++) { + if (!fd_table[i].is_used) { + return i; + } + } + return -1; +} + +/* File operations implementation */ +int file_open(const char* path, uint8_t mode) { + uint32_t inode = path_to_inode(path); + if (inode == FS_MAX_FILES) { + /* Create file if it doesn't exist and we're writing */ + if (mode & (FILE_MODE_WRITE | FILE_MODE_APPEND)) { + if (fs_create(path, FS_TYPE_FILE) == FS_SUCCESS) { + inode = path_to_inode(path); + } + } + if (inode == FS_MAX_FILES) return -1; + } + + /* Check if file is already open */ + for (int i = 0; i < MAX_OPEN_FILES; i++) { + if (fd_table[i].is_used && fd_table[i].inode == inode) { + if ((fd_table[i].mode & FILE_MODE_WRITE) || (mode & FILE_MODE_WRITE)) { + return -1; /* File already open for writing */ + } + } + } + + /* Get free file descriptor */ + int fd = get_free_fd(); + if (fd < 0) return -1; + + /* Initialize file descriptor */ + fd_table[fd].inode = inode; + fd_table[fd].offset = (mode & FILE_MODE_APPEND) ? fs_inodes[inode].size : 0; + fd_table[fd].mode = mode; + fd_table[fd].is_used = 1; + + return fd; +} + +int file_close(int fd) { + if (fd < 0 || fd >= MAX_OPEN_FILES || !fd_table[fd].is_used) { + return -1; + } + + fd_table[fd].is_used = 0; + return 0; +} + +int file_read(int fd, void* buffer, size_t size) { + if (fd < 0 || fd >= MAX_OPEN_FILES || !fd_table[fd].is_used || + !(fd_table[fd].mode & FILE_MODE_READ)) { + return -1; + } + + file_descriptor_t* desc = &fd_table[fd]; + fs_inode_t* inode = &fs_inodes[desc->inode]; + + /* Check if we're trying to read past end of file */ + if (desc->offset >= inode->size) { + return 0; + } + + /* Adjust size if it would read past end of file */ + if (desc->offset + size > inode->size) { + size = inode->size - desc->offset; + } + + /* Calculate block and offset */ + uint32_t start_block = desc->offset / FS_BLOCK_SIZE; + uint32_t block_offset = desc->offset % FS_BLOCK_SIZE; + uint32_t bytes_remaining = size; + uint8_t* buf_ptr = buffer; + + /* Find starting block */ + uint32_t current_block = inode->first_block; + for (uint32_t i = 0; i < start_block; i++) { + current_block = *(uint32_t*)(fs_blocks + current_block * FS_BLOCK_SIZE); + } + + /* Read data */ + while (bytes_remaining > 0 && current_block < FS_MAX_BLOCKS) { + uint32_t bytes_to_copy = FS_BLOCK_SIZE - block_offset; + if (bytes_to_copy > bytes_remaining) { + bytes_to_copy = bytes_remaining; + } + + memcpy(buf_ptr, fs_blocks + current_block * FS_BLOCK_SIZE + block_offset, + bytes_to_copy); + + bytes_remaining -= bytes_to_copy; + buf_ptr += bytes_to_copy; + desc->offset += bytes_to_copy; + + if (bytes_remaining > 0) { + current_block = *(uint32_t*)(fs_blocks + current_block * FS_BLOCK_SIZE); + block_offset = 0; + } + } + + return size - bytes_remaining; +} + +int file_write(int fd, const void* buffer, size_t size) { + if (fd < 0 || fd >= MAX_OPEN_FILES || !fd_table[fd].is_used || + !(fd_table[fd].mode & FILE_MODE_WRITE)) { + return -1; + } + + file_descriptor_t* desc = &fd_table[fd]; + fs_inode_t* inode = &fs_inodes[desc->inode]; + + /* Calculate required blocks */ + uint32_t end_offset = desc->offset + size; + uint32_t required_blocks = (end_offset + FS_BLOCK_SIZE - 1) / FS_BLOCK_SIZE; + + /* Allocate new blocks if needed */ + while (inode->block_count < required_blocks) { + uint32_t new_block = allocate_block(); + if (new_block == FS_MAX_BLOCKS) { + return -1; + } + + if (inode->block_count == 0) { + inode->first_block = new_block; + } else { + uint32_t current_block = inode->first_block; + for (uint32_t i = 1; i < inode->block_count; i++) { + current_block = *(uint32_t*)(fs_blocks + current_block * FS_BLOCK_SIZE); + } + *(uint32_t*)(fs_blocks + current_block * FS_BLOCK_SIZE) = new_block; + } + inode->block_count++; + } + + /* Write data */ + uint32_t start_block = desc->offset / FS_BLOCK_SIZE; + uint32_t block_offset = desc->offset % FS_BLOCK_SIZE; + uint32_t bytes_remaining = size; + const uint8_t* buf_ptr = buffer; + + uint32_t current_block = inode->first_block; + for (uint32_t i = 0; i < start_block; i++) { + current_block = *(uint32_t*)(fs_blocks + current_block * FS_BLOCK_SIZE); + } + + while (bytes_remaining > 0) { + uint32_t bytes_to_copy = FS_BLOCK_SIZE - block_offset; + if (bytes_to_copy > bytes_remaining) { + bytes_to_copy = bytes_remaining; + } + + memcpy(fs_blocks + current_block * FS_BLOCK_SIZE + block_offset, + buf_ptr, bytes_to_copy); + + bytes_remaining -= bytes_to_copy; + buf_ptr += bytes_to_copy; + desc->offset += bytes_to_copy; + + if (bytes_remaining > 0) { + current_block = *(uint32_t*)(fs_blocks + current_block * FS_BLOCK_SIZE); + block_offset = 0; + } + } + + /* Update file size if needed */ + if (desc->offset > inode->size) { + inode->size = desc->offset; + } + + return size; +} + +int file_seek(int fd, int32_t offset, int whence) { + if (fd < 0 || fd >= MAX_OPEN_FILES || !fd_table[fd].is_used) { + return -1; + } + + file_descriptor_t* desc = &fd_table[fd]; + fs_inode_t* inode = &fs_inodes[desc->inode]; + + int32_t new_offset; + switch (whence) { + case SEEK_SET: + new_offset = offset; + break; + case SEEK_CUR: + new_offset = desc->offset + offset; + break; + case SEEK_END: + new_offset = inode->size + offset; + break; + default: + return -1; + } + + if (new_offset < 0 || (uint32_t)new_offset > inode->size) { + return -1; + } + + desc->offset = new_offset; + return new_offset; +} + +/* File operations table */ +file_operations_t file_ops = { + .open = file_open, + .close = file_close, + .read = file_read, + .write = file_write, + .seek = file_seek +}; diff --git a/file_io.h b/file_io.h new file mode 100644 index 0000000..fb365bc --- /dev/null +++ b/file_io.h @@ -0,0 +1,26 @@ +#ifndef _FILE_IO_H_ +#define _FILE_IO_H_ + +#include +#include + +/* File modes */ +#define FILE_MODE_READ 0x01 +#define FILE_MODE_WRITE 0x02 +#define FILE_MODE_APPEND 0x04 + +/* Seek modes */ +#define SEEK_SET 0 +#define SEEK_CUR 1 +#define SEEK_END 2 + +/* File operations */ +typedef struct file_operations { + int (*open)(const char* path, uint8_t mode); + int (*close)(int fd); + int (*read)(int fd, void* buffer, size_t size); + int (*write)(int fd, const void* buffer, size_t size); + int (*seek)(int fd, int32_t offset, int whence); +} file_operations_t; + +#endif /* _FILE_IO_H_ */ diff --git a/file_io.o b/file_io.o new file mode 100644 index 0000000..51e7381 Binary files /dev/null and b/file_io.o differ diff --git a/filesystem.c b/filesystem.c new file mode 100644 index 0000000..a653adc --- /dev/null +++ b/filesystem.c @@ -0,0 +1,253 @@ +#include +#include +#include +#include +#include + +/* File system structures in memory */ +uint8_t* fs_blocks; +fs_inode_t* fs_inodes; +uint8_t* block_bitmap; +fs_file_handle_t open_files[FS_MAX_OPEN_FILES]; +uint32_t current_directory; + +/* Initialize file system */ +void init_filesystem(void) { + /* Allocate memory for file system structures */ + fs_blocks = kmalloc(FS_BLOCK_SIZE * FS_MAX_BLOCKS); + fs_inodes = kmalloc(sizeof(fs_inode_t) * FS_MAX_FILES); + block_bitmap = kmalloc(FS_MAX_BLOCKS / 8); + + if (!fs_blocks || !fs_inodes || !block_bitmap) { + print_string("Failed to allocate file system memory\n"); + return; + } + + /* Clear all structures */ + memset(fs_blocks, 0, FS_BLOCK_SIZE * FS_MAX_BLOCKS); + memset(fs_inodes, 0, sizeof(fs_inode_t) * FS_MAX_FILES); + memset(block_bitmap, 0, FS_MAX_BLOCKS / 8); + memset(open_files, 0, sizeof(open_files)); + + /* Create root directory */ + fs_format(); + print_string("File system initialized\n"); +} + +/* Format file system */ +int fs_format(void) { + /* Clear all structures */ + memset(fs_blocks, 0, FS_BLOCK_SIZE * FS_MAX_BLOCKS); + memset(fs_inodes, 0, sizeof(fs_inode_t) * FS_MAX_FILES); + memset(block_bitmap, 0, FS_MAX_BLOCKS / 8); + + /* Create root directory */ + fs_inode_t* root = &fs_inodes[0]; + strcpy(root->name, "/"); + root->type = FS_TYPE_DIRECTORY; + root->permissions = FS_PERM_READ | FS_PERM_WRITE | FS_PERM_EXECUTE; + root->size = 0; + root->block_count = 0; + root->first_block = 0; + root->parent_inode = 0; + root->create_time = 0; // TODO: Add real time support + root->modify_time = 0; + + current_directory = 0; + return FS_SUCCESS; +} + +/* Helper functions */ +uint32_t allocate_block(void) { + for (uint32_t i = 0; i < FS_MAX_BLOCKS; i++) { + if (!(block_bitmap[i / 8] & (1 << (i % 8)))) { + block_bitmap[i / 8] |= (1 << (i % 8)); + return i; + } + } + return FS_MAX_BLOCKS; +} + +void free_block(uint32_t block) { + if (block < FS_MAX_BLOCKS) { + block_bitmap[block / 8] &= ~(1 << (block % 8)); + } +} + +static uint32_t find_free_inode(void) { + for (uint32_t i = 1; i < FS_MAX_FILES; i++) { + if (fs_inodes[i].type == 0) { + return i; + } + } + return FS_MAX_FILES; +} + +uint32_t path_to_inode(const char* path) { + if (!path || !*path) return FS_MAX_FILES; + + if (strcmp(path, "/") == 0) return 0; + + char temp_path[FS_MAX_PATH]; + strncpy(temp_path, path, FS_MAX_PATH - 1); + temp_path[FS_MAX_PATH - 1] = '\0'; + + uint32_t current = current_directory; + char* token = strtok(temp_path, "/"); + + while (token) { + if (strcmp(token, ".") == 0) { + /* Current directory */ + } else if (strcmp(token, "..") == 0) { + /* Parent directory */ + current = fs_inodes[current].parent_inode; + } else { + /* Search in current directory */ + int found = 0; + for (uint32_t i = 0; i < FS_MAX_FILES; i++) { + if (fs_inodes[i].type != 0 && + fs_inodes[i].parent_inode == current && + strcmp(fs_inodes[i].name, token) == 0) { + current = i; + found = 1; + break; + } + } + if (!found) return FS_MAX_FILES; + } + token = strtok(NULL, "/"); + } + + return current; +} + +/* File operations */ +int fs_create(const char* path, uint8_t type) { + if (!path || !*path) return FS_ERROR_INVALID; + + /* Check if file already exists */ + if (path_to_inode(path) != FS_MAX_FILES) { + return FS_ERROR_EXISTS; + } + + /* Find last separator to get parent directory and filename */ + const char* last_slash = strrchr(path, '/'); + const char* filename = last_slash ? last_slash + 1 : path; + + /* Get parent directory inode */ + uint32_t parent; + if (last_slash) { + char parent_path[FS_MAX_PATH]; + size_t parent_len = last_slash - path; + if (parent_len == 0) parent_len = 1; /* Root directory */ + + strncpy(parent_path, path, parent_len); + parent_path[parent_len] = '\0'; + + parent = path_to_inode(parent_path); + if (parent == FS_MAX_FILES) return FS_ERROR_NOT_FOUND; + } else { + parent = current_directory; + } + + /* Allocate new inode */ + uint32_t inode = find_free_inode(); + if (inode == FS_MAX_FILES) return FS_ERROR_FULL; + + /* Initialize inode */ + fs_inode_t* new_inode = &fs_inodes[inode]; + strncpy(new_inode->name, filename, FS_MAX_FILENAME - 1); + new_inode->name[FS_MAX_FILENAME - 1] = '\0'; + + new_inode->permissions = FS_PERM_READ | FS_PERM_WRITE; + if (type == FS_TYPE_DIRECTORY) { + new_inode->permissions |= FS_PERM_EXECUTE; + } + new_inode->size = 0; + new_inode->block_count = 0; + new_inode->first_block = 0; + new_inode->parent_inode = parent; + new_inode->create_time = 0; // TODO: Add real time support + new_inode->modify_time = 0; + + return FS_SUCCESS; +} + +int fs_delete(const char* path) { + uint32_t inode = path_to_inode(path); + if (inode == FS_MAX_FILES) return FS_ERROR_NOT_FOUND; + + fs_inode_t* node = &fs_inodes[inode]; + + /* Free all blocks */ + uint32_t current_block = node->first_block; + for (uint32_t i = 0; i < node->block_count; i++) { + free_block(current_block); + /* Get next block from block list */ + current_block = *(uint32_t*)(fs_blocks + current_block * FS_BLOCK_SIZE); + } + + /* Clear inode */ + memset(node, 0, sizeof(fs_inode_t)); + + return FS_SUCCESS; +} + +/* Directory operations */ +int fs_mkdir(const char* path) { + return fs_create(path, FS_TYPE_DIRECTORY); +} + +int fs_rmdir(const char* path) { + uint32_t inode = path_to_inode(path); + if (inode == FS_MAX_FILES) return FS_ERROR_NOT_FOUND; + + fs_inode_t* node = &fs_inodes[inode]; + if (node->type != FS_TYPE_DIRECTORY) return FS_ERROR_INVALID; + + /* Check if directory is empty */ + for (uint32_t i = 0; i < FS_MAX_FILES; i++) { + if (fs_inodes[i].type != 0 && fs_inodes[i].parent_inode == inode) { + return FS_ERROR_INVALID; /* Directory not empty */ + } + } + + return fs_delete(path); +} + +int fs_list_dir(const char* path, char* buffer, size_t size) { + uint32_t inode = path ? path_to_inode(path) : current_directory; + if (inode == FS_MAX_FILES) return FS_ERROR_NOT_FOUND; + + fs_inode_t* node = &fs_inodes[inode]; + if (node->type != FS_TYPE_DIRECTORY) return FS_ERROR_INVALID; + + size_t offset = 0; + + /* List all entries in directory */ + for (uint32_t i = 0; i < FS_MAX_FILES; i++) { + if (fs_inodes[i].type != 0 && fs_inodes[i].parent_inode == inode) { + const char* type_str = (fs_inodes[i].type == FS_TYPE_DIRECTORY) ? "DIR " : "FILE"; + int written = snprintf(buffer + offset, size - offset, + "%s %s %u bytes\n", + type_str, fs_inodes[i].name, fs_inodes[i].size); + if (written < 0 || (size_t)written >= size - offset) break; + offset += written; + } + } + + return FS_SUCCESS; +} + +/* Path operations */ +int fs_change_dir(const char* path) { + uint32_t inode = path_to_inode(path); + if (inode == FS_MAX_FILES) return FS_ERROR_NOT_FOUND; + + if (fs_inodes[inode].type != FS_TYPE_DIRECTORY) { + return FS_ERROR_INVALID; + } + + current_directory = inode; + return FS_SUCCESS; +} diff --git a/filesystem.h b/filesystem.h new file mode 100644 index 0000000..83565fd --- /dev/null +++ b/filesystem.h @@ -0,0 +1,87 @@ +#ifndef _FILESYSTEM_H_ +#define _FILESYSTEM_H_ + +#include +#include + +/* File system constants */ +#define FS_MAX_FILENAME 64 +#define FS_MAX_PATH 256 +#define FS_BLOCK_SIZE 4096 +#define FS_MAX_BLOCKS 65536 +#define FS_MAX_FILES 1024 +#define FS_MAX_OPEN_FILES 32 + +/* File types */ +#define FS_TYPE_FILE 1 +#define FS_TYPE_DIRECTORY 2 + +/* File permissions */ +#define FS_PERM_READ 0x04 +#define FS_PERM_WRITE 0x02 +#define FS_PERM_EXECUTE 0x01 + +/* Error codes */ +#define FS_SUCCESS 0 +#define FS_ERROR_NOT_FOUND -1 +#define FS_ERROR_EXISTS -2 +#define FS_ERROR_FULL -3 +#define FS_ERROR_INVALID -4 +#define FS_ERROR_PERM -5 + +/* File system structures */ +typedef struct { + char name[FS_MAX_FILENAME]; + uint8_t type; + uint8_t permissions; + uint32_t size; + uint32_t block_count; + uint32_t first_block; + uint32_t parent_inode; + uint32_t create_time; + uint32_t modify_time; +} fs_inode_t; + +typedef struct { + uint32_t inode; + uint32_t offset; + uint8_t mode; /* read/write/append */ + uint8_t flags; +} fs_file_handle_t; + +/* Global filesystem state */ +extern fs_inode_t* fs_inodes; +extern uint32_t current_directory; +extern uint8_t* fs_blocks; +extern uint32_t path_to_inode(const char* path); +extern uint32_t allocate_block(void); +extern void free_block(uint32_t block); + +/* File system functions */ +void init_filesystem(void); +int fs_format(void); + +/* File operations */ +int fs_create(const char* path, uint8_t type); +int fs_delete(const char* path); +int fs_open(const char* path, uint8_t mode); +int fs_close(int handle); +int fs_read(int handle, void* buffer, size_t size); +int fs_write(int handle, const void* buffer, size_t size); +int fs_seek(int handle, int32_t offset, int whence); + +/* Directory operations */ +int fs_mkdir(const char* path); +int fs_rmdir(const char* path); +int fs_list_dir(const char* path, char* buffer, size_t size); + +/* Path operations */ +int fs_get_current_dir(char* buffer, size_t size); +int fs_change_dir(const char* path); + +/* Utility functions */ +int fs_exists(const char* path); +int fs_get_type(const char* path); +int fs_get_size(const char* path); + +#endif /* _FILESYSTEM_H_ */ diff --git a/filesystem.o b/filesystem.o new file mode 100644 index 0000000..605942a Binary files /dev/null and b/filesystem.o differ diff --git a/fs_commands.c b/fs_commands.c new file mode 100644 index 0000000..e1e05eb --- /dev/null +++ b/fs_commands.c @@ -0,0 +1,169 @@ +#include +#include + +/* Maximum path buffer size */ +#define PATH_BUF_SIZE FS_MAX_PATH + +int cmd_ls(int argc, char* argv[]) { + char buffer[4096]; + const char* path = (argc > 1) ? argv[1] : NULL; + + int result = fs_list_dir(path, buffer, sizeof(buffer)); + if (result != FS_SUCCESS) { + switch (result) { + case FS_ERROR_NOT_FOUND: + shell_printf("ls: directory not found: %s\n", path); + break; + case FS_ERROR_INVALID: + shell_printf("ls: not a directory: %s\n", path); + break; + default: + shell_puts("ls: unknown error\n"); + } + return 1; + } + + shell_puts(buffer); + return 0; +} + +int cmd_cd(int argc, char* argv[]) { + if (argc != 2) { + shell_puts("Usage: cd \n"); + return 1; + } + + int result = fs_change_dir(argv[1]); + if (result != FS_SUCCESS) { + switch (result) { + case FS_ERROR_NOT_FOUND: + shell_printf("cd: directory not found: %s\n", argv[1]); + break; + case FS_ERROR_INVALID: + shell_printf("cd: not a directory: %s\n", argv[1]); + break; + default: + shell_puts("cd: unknown error\n"); + } + return 1; + } + + return 0; +} + +int cmd_pwd(int argc, char* argv[]) { + (void)argc; + (void)argv; + + char buffer[PATH_BUF_SIZE]; + int result = fs_get_current_dir(buffer, sizeof(buffer)); + if (result != FS_SUCCESS) { + shell_puts("pwd: error getting current directory\n"); + return 1; + } + + shell_printf("%s\n", buffer); + return 0; +} + +int cmd_mkdir(int argc, char* argv[]) { + if (argc != 2) { + shell_puts("Usage: mkdir \n"); + return 1; + } + + int result = fs_mkdir(argv[1]); + if (result != FS_SUCCESS) { + switch (result) { + case FS_ERROR_EXISTS: + shell_printf("mkdir: directory already exists: %s\n", argv[1]); + break; + case FS_ERROR_INVALID: + shell_printf("mkdir: invalid directory name: %s\n", argv[1]); + break; + case FS_ERROR_FULL: + shell_puts("mkdir: file system is full\n"); + break; + default: + shell_puts("mkdir: unknown error\n"); + } + return 1; + } + + return 0; +} + +int cmd_rmdir(int argc, char* argv[]) { + if (argc != 2) { + shell_puts("Usage: rmdir \n"); + return 1; + } + + int result = fs_rmdir(argv[1]); + if (result != FS_SUCCESS) { + switch (result) { + case FS_ERROR_NOT_FOUND: + shell_printf("rmdir: directory not found: %s\n", argv[1]); + break; + case FS_ERROR_INVALID: + shell_printf("rmdir: not a directory or directory not empty: %s\n", argv[1]); + break; + default: + shell_puts("rmdir: unknown error\n"); + } + return 1; + } + + return 0; +} + +int cmd_touch(int argc, char* argv[]) { + if (argc != 2) { + shell_puts("Usage: touch \n"); + return 1; + } + + int result = fs_create(argv[1], FS_TYPE_FILE); + if (result != FS_SUCCESS) { + switch (result) { + case FS_ERROR_EXISTS: + shell_printf("touch: file already exists: %s\n", argv[1]); + break; + case FS_ERROR_INVALID: + shell_printf("touch: invalid file name: %s\n", argv[1]); + break; + case FS_ERROR_FULL: + shell_puts("touch: file system is full\n"); + break; + default: + shell_puts("touch: unknown error\n"); + } + return 1; + } + + return 0; +} + +int cmd_rm(int argc, char* argv[]) { + if (argc != 2) { + shell_puts("Usage: rm \n"); + return 1; + } + + int result = fs_delete(argv[1]); + if (result != FS_SUCCESS) { + switch (result) { + case FS_ERROR_NOT_FOUND: + shell_printf("rm: file not found: %s\n", argv[1]); + break; + case FS_ERROR_INVALID: + shell_printf("rm: is a directory: %s\n", argv[1]); + break; + default: + shell_puts("rm: unknown error\n"); + } + return 1; + } + + return 0; +} diff --git a/fs_utils.c b/fs_utils.c new file mode 100644 index 0000000..74e68a4 --- /dev/null +++ b/fs_utils.c @@ -0,0 +1,184 @@ +#include +#include + +/* File system utilities */ + +/* Convert absolute path to relative path */ +static void make_relative_path(const char* abs_path, char* rel_path, size_t size) { + if (strcmp(abs_path, "/") == 0) { + strncpy(rel_path, ".", size - 1); + rel_path[size - 1] = '\0'; + return; + } + + /* Skip leading slash */ + if (abs_path[0] == '/') { + abs_path++; + } + + strncpy(rel_path, abs_path, size - 1); + rel_path[size - 1] = '\0'; +} + +/* Convert relative path to absolute path */ +static void make_absolute_path(const char* rel_path, char* abs_path, size_t size) { + if (rel_path[0] == '/') { + strncpy(abs_path, rel_path, size - 1); + abs_path[size - 1] = '\0'; + return; + } + + /* Get current directory path */ + char current_path[FS_MAX_PATH]; + fs_get_current_dir(current_path, sizeof(current_path)); + + if (strcmp(rel_path, ".") == 0) { + strncpy(abs_path, current_path, size - 1); + abs_path[size - 1] = '\0'; + return; + } + + /* Combine paths */ + snprintf(abs_path, size, "%s/%s", current_path, rel_path); +} + +/* Normalize path (remove . and .. components) */ +static void normalize_path(char* path) { + char* src = path; + char* dst = path; + char* last = NULL; + char* components[FS_MAX_PATH / 2]; + int component_count = 0; + + /* Skip leading slash */ + if (*src == '/') { + *dst++ = *src++; + } + + while (*src) { + if (*src == '/') { + src++; + continue; + } + + /* Mark start of component */ + last = dst; + components[component_count++] = dst; + + /* Copy until next slash or end */ + while (*src && *src != '/') { + *dst++ = *src++; + } + + /* Handle . and .. */ + if (last && dst - last == 1 && *last == '.') { + /* Remove . component */ + dst = last; + component_count--; + } else if (last && dst - last == 2 && last[0] == '.' && last[1] == '.') { + /* Remove .. and previous component */ + if (component_count >= 2) { + dst = components[component_count - 2]; + component_count -= 2; + } + } + + if (*src && dst > path && dst[-1] != '/') { + *dst++ = '/'; + } + } + + /* Remove trailing slash unless root */ + if (dst > path + 1 && dst[-1] == '/') { + dst--; + } + + *dst = '\0'; +} + +/* Get parent directory path */ +static void get_parent_path(const char* path, char* parent, size_t size) { + strncpy(parent, path, size - 1); + parent[size - 1] = '\0'; + + char* last_slash = strrchr(parent, '/'); + if (last_slash == parent) { + /* Root directory */ + parent[1] = '\0'; + } else if (last_slash) { + *last_slash = '\0'; + } +} + +/* Get file name from path */ +static void get_file_name(const char* path, char* name, size_t size) { + const char* last_slash = strrchr(path, '/'); + const char* filename = last_slash ? last_slash + 1 : path; + + strncpy(name, filename, size - 1); + name[size - 1] = '\0'; +} + +/* File system API implementations */ + +int fs_get_current_dir(char* buffer, size_t size) { + if (!buffer || size == 0) return FS_ERROR_INVALID; + + char path[FS_MAX_PATH]; + uint32_t current = current_directory; + int path_len = 0; + + /* Build path from current directory up to root */ + while (current != 0) { + fs_inode_t* node = &fs_inodes[current]; + int name_len = strlen(node->name); + + /* Check if we have enough space */ + if (path_len + name_len + 1 >= FS_MAX_PATH) { + return FS_ERROR_INVALID; + } + + /* Move existing path components right */ + memmove(path + name_len + 1, path, path_len); + + /* Add current component */ + memcpy(path, node->name, name_len); + path[name_len] = '/'; + path_len += name_len + 1; + + current = node->parent_inode; + } + + /* Add root slash if not root directory */ + if (path_len == 0) { + path[path_len++] = '/'; + } + + path[path_len] = '\0'; + + /* Copy to output buffer */ + if ((size_t)path_len >= size) { + return FS_ERROR_INVALID; + } + + strcpy(buffer, path); + return FS_SUCCESS; +} + +int fs_exists(const char* path) { + return path_to_inode(path) != FS_MAX_FILES; +} + +int fs_get_type(const char* path) { + uint32_t inode = path_to_inode(path); + if (inode == FS_MAX_FILES) return FS_ERROR_NOT_FOUND; + + return fs_inodes[inode].type; +} + +int fs_get_size(const char* path) { + uint32_t inode = path_to_inode(path); + if (inode == FS_MAX_FILES) return FS_ERROR_NOT_FOUND; + + return fs_inodes[inode].size; +} diff --git a/fs_utils.o b/fs_utils.o new file mode 100644 index 0000000..0e9e779 Binary files /dev/null and b/fs_utils.o differ diff --git a/init.c b/init.c new file mode 100644 index 0000000..e201ed8 --- /dev/null +++ b/init.c @@ -0,0 +1,40 @@ +#include +#include +#include +#include +#include +#include + +void init_system(void) { + /* Initialize hardware-specific features */ + init_keyboard(); + + /* Set up interrupt handlers */ + // TODO: Implement IDT and interrupt handlers + + /* Initialize system clock */ + // TODO: Implement PIT configuration + + print_string("Keyboard initialized\n"); +} + +void init_memory(void) { + /* Initialize physical memory manager with 64MB of RAM for testing */ + init_physical_memory(64 * 1024 * 1024); + + /* Initialize paging system */ + init_paging(); +} + +void init_process(void) { + init_process_manager(); + + /* Create initial system process */ + process_t* init = create_process("init", NULL, 4096); + if (!init) { + print_string("Failed to create initial process\n"); + return; + } + + print_string("Initial process created\n"); +} diff --git a/init.o b/init.o new file mode 100644 index 0000000..41e12e7 Binary files /dev/null and b/init.o differ diff --git a/kernel.c b/kernel.c new file mode 100644 index 0000000..d87b2c1 --- /dev/null +++ b/kernel.c @@ -0,0 +1,33 @@ +#include +#include +#include +#include +#include + +/* Kernel entry point */ +void kmain(void) { + /* Initialize video first for debugging output */ + init_video(); + + /* Initialize core systems */ + init_system(); + init_memory(); + init_process(); + + /* Display welcome message with amethyst theme */ + const char* welcome = "Welcome to AmethystOS\n" + "Version: " AMETHYST_VERSION "\n" + "---------------------------\n" + "Initializing system components..."; + print_string(welcome); + + /* Initialize and start the shell */ + init_shell(); + shell_main(); + + /* Main kernel loop */ + while(1) { + /* TODO: Implement process scheduling and system calls */ + __asm__ volatile("hlt"); + } +} diff --git a/kernel.h b/kernel.h new file mode 100644 index 0000000..8bc00c2 --- /dev/null +++ b/kernel.h @@ -0,0 +1,30 @@ +#ifndef _KERNEL_H_ +#define _KERNEL_H_ + +/* System constants */ +#define AMETHYST_VERSION "0.1.0" +#define MAX_PROCESSES 32 +#define STACK_SIZE 4096 + +/* Process states */ +#define PR_FREE 0 +#define PR_CURR 1 +#define PR_READY 2 +#define PR_RECV 3 +#define PR_SLEEP 4 +#define PR_SUSP 5 +#define PR_WAIT 6 +#define PR_RECTIM 7 + +/* Color theme constants */ +#define AMETHYST_PURPLE 0x9966CC +#define AMETHYST_DARK 0x663399 +#define AMETHYST_LIGHT 0xCC99FF + +/* Function prototypes */ +void kmain(void); +void init_system(void); +void init_memory(void); +void init_process(void); + +#endif /* _KERNEL_H_ */ diff --git a/kernel.o b/kernel.o new file mode 100644 index 0000000..796a503 Binary files /dev/null and b/kernel.o differ diff --git a/keyboard.c b/keyboard.c new file mode 100644 index 0000000..b18c56c --- /dev/null +++ b/keyboard.c @@ -0,0 +1,199 @@ +#include +#include + +/* US QWERTY keyboard layout - scancode to ASCII mapping */ +static const char scancode_to_ascii[] = { + 0, /* Error */ + 27, /* Escape */ + '1', '2', '3', '4', '5', '6', '7', '8', '9', '0', '-', '=', + '\b', /* Backspace */ + '\t', /* Tab */ + 'q', 'w', 'e', 'r', 't', 'y', 'u', 'i', 'o', 'p', '[', ']', '\n', + 0, /* Control */ + 'a', 's', 'd', 'f', 'g', 'h', 'j', 'k', 'l', ';', '\'', + '`', + 0, /* Left shift */ + '\\', + 'z', 'x', 'c', 'v', 'b', 'n', 'm', ',', '.', '/', + 0, /* Right shift */ + '*', + 0, /* Alt */ + ' ', /* Space */ + 0, /* Caps lock */ + 0, /* F1 */ + 0, /* F2 */ + 0, /* F3 */ + 0, /* F4 */ + 0, /* F5 */ + 0, /* F6 */ + 0, /* F7 */ + 0, /* F8 */ + 0, /* F9 */ + 0 /* F10 */ +}; + +/* Shifted characters */ +static const char scancode_to_ascii_shifted[] = { + 0, /* Error */ + 27, /* Escape */ + '!', '@', '#', '$', '%', '^', '&', '*', '(', ')', '_', '+', + '\b', /* Backspace */ + '\t', /* Tab */ + 'Q', 'W', 'E', 'R', 'T', 'Y', 'U', 'I', 'O', 'P', '{', '}', '\n', + 0, /* Control */ + 'A', 'S', 'D', 'F', 'G', 'H', 'J', 'K', 'L', ':', '"', + '~', + 0, /* Left shift */ + '|', + 'Z', 'X', 'C', 'V', 'B', 'N', 'M', '<', '>', '?', + 0, /* Right shift */ + '*', + 0, /* Alt */ + ' ', /* Space */ + 0, /* Caps lock */ + 0, /* F1 */ + 0, /* F2 */ + 0, /* F3 */ + 0, /* F4 */ + 0, /* F5 */ + 0, /* F6 */ + 0, /* F7 */ + 0, /* F8 */ + 0, /* F9 */ + 0 /* F10 */ +}; + +/* Key states */ +static uint8_t shift_pressed = 0; +static uint8_t ctrl_pressed = 0; +static uint8_t alt_pressed = 0; +static uint8_t caps_lock_on = 0; + +/* Port I/O functions */ +static inline uint8_t inb(uint16_t port) { + uint8_t value; + asm volatile("inb %1, %0" : "=a"(value) : "Nd"(port)); + return value; +} + +static inline void outb(uint16_t port, uint8_t value) { + asm volatile("outb %0, %1" : : "a"(value), "Nd"(port)); +} + +void init_keyboard(void) { + /* Reset keyboard */ + outb(KEYBOARD_COMMAND_PORT, 0xFF); + + /* Wait for acknowledgment */ + while ((inb(KEYBOARD_STATUS_PORT) & 0x01)) { + inb(KEYBOARD_DATA_PORT); + } + + /* Set default LED state */ + keyboard_set_leds(0); +} + +char keyboard_read(void) { + static int is_extended = 0; + + /* Wait for key */ + while (!(inb(KEYBOARD_STATUS_PORT) & 0x01)); + + /* Read scancode */ + uint8_t scancode = inb(KEYBOARD_DATA_PORT); + + /* Handle extended scancodes */ + if (scancode == 0xE0) { + is_extended = 1; + return 0; + } + + /* If this is the second byte of an extended scancode */ + if (is_extended) { + is_extended = 0; + /* Convert extended scancode to special character sequences */ + switch (scancode) { + case KEY_UP: return '\x1B[A'; + case KEY_DOWN: return '\x1B[B'; + case KEY_RIGHT: return '\x1B[C'; + case KEY_LEFT: return '\x1B[D'; + case KEY_HOME: return '\x1B[H'; + case KEY_END: return '\x1B[F'; + default: return 0; + } + } + + /* Handle key release */ + if (scancode & 0x80) { + scancode &= 0x7F; + switch (scancode) { + case KEY_LSHIFT: + case KEY_RSHIFT: + shift_pressed = 0; + break; + case KEY_LCTRL: + ctrl_pressed = 0; + break; + case KEY_LALT: + alt_pressed = 0; + break; + } + return 0; + } + + /* Handle key press */ + switch (scancode) { + case KEY_LSHIFT: + case KEY_RSHIFT: + shift_pressed = 1; + return 0; + case KEY_LCTRL: + ctrl_pressed = 1; + return 0; + case KEY_LALT: + alt_pressed = 1; + return 0; + case KEY_CAPS_LOCK: + caps_lock_on = !caps_lock_on; + keyboard_set_leds(caps_lock_on ? LED_CAPS_LOCK : 0); + return 0; + } + + /* Convert scancode to ASCII */ + if (scancode >= sizeof(scancode_to_ascii)) return 0; + + char c; + if (shift_pressed) { + c = scancode_to_ascii_shifted[scancode]; + } else { + c = scancode_to_ascii[scancode]; + } + + /* Handle caps lock */ + if (caps_lock_on && ((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z'))) { + c ^= 0x20; /* Toggle case */ + } + + return c; +} + +void keyboard_set_leds(uint8_t led_state) { + outb(KEYBOARD_DATA_PORT, 0xED); + outb(KEYBOARD_DATA_PORT, led_state & 0x07); +} + +int is_shift_pressed(void) { + return shift_pressed; +} + +int is_caps_lock_on(void) { + return caps_lock_on; +} + +int is_ctrl_pressed(void) { + return ctrl_pressed; +} + +int is_alt_pressed(void) { + return alt_pressed; +} diff --git a/keyboard.h b/keyboard.h new file mode 100644 index 0000000..ee4b608 --- /dev/null +++ b/keyboard.h @@ -0,0 +1,60 @@ +#ifndef _KEYBOARD_H_ +#define _KEYBOARD_H_ + +#include + +/* Keyboard ports */ +#define KEYBOARD_DATA_PORT 0x60 +#define KEYBOARD_STATUS_PORT 0x64 +#define KEYBOARD_COMMAND_PORT 0x64 + +/* Keyboard LED states */ +#define LED_SCROLL_LOCK 0x01 +#define LED_NUM_LOCK 0x02 +#define LED_CAPS_LOCK 0x04 + +/* Special keys */ +#define KEY_ESCAPE 0x01 +#define KEY_BACKSPACE 0x0E +#define KEY_TAB 0x0F +#define KEY_ENTER 0x1C +#define KEY_LCTRL 0x1D +#define KEY_LSHIFT 0x2A +#define KEY_RSHIFT 0x36 +#define KEY_LALT 0x38 +#define KEY_CAPS_LOCK 0x3A +#define KEY_F1 0x3B +#define KEY_F2 0x3C +#define KEY_F3 0x3D +#define KEY_F4 0x3E +#define KEY_F5 0x3F +#define KEY_F6 0x40 +#define KEY_F7 0x41 +#define KEY_F8 0x42 +#define KEY_F9 0x43 +#define KEY_F10 0x44 +#define KEY_F11 0x57 +#define KEY_F12 0x58 + +/* Extended (E0) scancodes */ +#define KEY_UP 0x48 +#define KEY_DOWN 0x50 +#define KEY_LEFT 0x4B +#define KEY_RIGHT 0x4D +#define KEY_HOME 0x47 +#define KEY_END 0x4F +#define KEY_DELETE 0x53 + +/* Function prototypes */ +void init_keyboard(void); +char keyboard_read(void); +void keyboard_set_leds(uint8_t led_state); +int is_key_pressed(uint8_t scancode); + +/* Key state functions */ +int is_shift_pressed(void); +int is_caps_lock_on(void); +int is_ctrl_pressed(void); +int is_alt_pressed(void); + +#endif /* _KEYBOARD_H_ */ diff --git a/libstring.a b/libstring.a new file mode 100644 index 0000000..3888e7a Binary files /dev/null and b/libstring.a differ diff --git a/link.ld b/link.ld new file mode 100644 index 0000000..dc92ada --- /dev/null +++ b/link.ld @@ -0,0 +1,23 @@ +ENTRY(_start) + +SECTIONS { + . = 1M; + + .text BLOCK(4K) : ALIGN(4K) { + *(.multiboot) + *(.text) + } + + .rodata BLOCK(4K) : ALIGN(4K) { + *(.rodata) + } + + .data BLOCK(4K) : ALIGN(4K) { + *(.data) + } + + .bss BLOCK(4K) : ALIGN(4K) { + *(COMMON) + *(.bss) + } +} diff --git a/memory.c b/memory.c new file mode 100644 index 0000000..1ea2dbf --- /dev/null +++ b/memory.c @@ -0,0 +1,92 @@ +#include +#include + +/* Memory bitmap - each bit represents a 4KB block */ +#define MEMORY_BITMAP_SIZE 32768 /* Supports up to 4GB of RAM */ +static uint8_t memory_bitmap[MEMORY_BITMAP_SIZE]; +static uint32_t total_blocks; +static uint32_t used_blocks; + +/* Helper functions */ +static void set_block(uint32_t bit) { + memory_bitmap[bit / 8] |= (1 << (bit % 8)); + used_blocks++; +} + +static void clear_block(uint32_t bit) { + memory_bitmap[bit / 8] &= ~(1 << (bit % 8)); + used_blocks--; +} + +static uint32_t test_block(uint32_t bit) { + return memory_bitmap[bit / 8] & (1 << (bit % 8)); +} + +static int32_t find_free_blocks(uint32_t count) { + if (count == 0) return -1; + + uint32_t first_free = 0; + uint32_t free_count = 0; + + for (uint32_t i = 0; i < total_blocks; i++) { + if (!test_block(i)) { + if (free_count == 0) first_free = i; + free_count++; + if (free_count == count) return first_free; + } else { + free_count = 0; + } + } + return -1; +} + +void init_physical_memory(uint32_t mem_upper) { + total_blocks = mem_upper / BLOCK_SIZE; + used_blocks = 0; + + /* Mark all memory as free initially */ + for (uint32_t i = 0; i < MEMORY_BITMAP_SIZE; i++) { + memory_bitmap[i] = 0; + } + + /* Mark kernel space as used */ + uint32_t kernel_blocks = (KERNEL_VIRTUAL_BASE + BLOCK_SIZE - 1) / BLOCK_SIZE; + for (uint32_t i = 0; i < kernel_blocks; i++) { + set_block(i); + } + + print_string("Physical memory manager initialized\n"); + print_string("Total memory: "); + // TODO: Add function to print numbers + print_string(" KB\n"); +} + +void* kmalloc(size_t size) { + if (size == 0) return NULL; + + /* Calculate number of blocks needed */ + uint32_t blocks = (size + BLOCK_SIZE - 1) / BLOCK_SIZE; + + /* Find free block(s) */ + int32_t block_index = find_free_blocks(blocks); + if (block_index < 0) return NULL; + + /* Mark blocks as used */ + for (uint32_t i = 0; i < blocks; i++) { + set_block(block_index + i); + } + + return (void*)(block_index * BLOCK_SIZE); +} + +void kfree(void* ptr) { + if (!ptr) return; + + uint32_t block_index = (uint32_t)ptr / BLOCK_SIZE; + + /* Find end of allocation by looking for next used block */ + while (block_index < total_blocks && test_block(block_index)) { + clear_block(block_index); + block_index++; + } +} diff --git a/memory.h b/memory.h new file mode 100644 index 0000000..e511be8 --- /dev/null +++ b/memory.h @@ -0,0 +1,36 @@ +#ifndef _MEMORY_H_ +#define _MEMORY_H_ + +#include +#include + +/* Memory constants */ +#define PAGE_SIZE 4096 +#define KERNEL_START 0x100000 +#define KERNEL_VIRTUAL_BASE 0xC0000000 + +/* Page flags */ +#define PAGE_PRESENT 0x1 +#define PAGE_WRITE 0x2 +#define PAGE_USER 0x4 + +/* Memory bitmap constants */ +#define BLOCKS_PER_BYTE 8 +#define BLOCK_SIZE 4096 +#define BLOCK_ALIGN BLOCK_SIZE + +/* Paging structures */ +extern uint32_t page_directory[1024]; + +/* Memory manager functions */ +void init_physical_memory(uint32_t mem_upper); +void* kmalloc(size_t size); +void kfree(void* ptr); + +/* Page directory functions */ +void init_paging(void); +void switch_page_directory(uint32_t* page_directory); +void map_page(void* physical, void* virtual, uint32_t flags); +void unmap_page(void* virtual); + +#endif /* _MEMORY_H_ */ diff --git a/memory.o b/memory.o new file mode 100644 index 0000000..5ffc2ae Binary files /dev/null and b/memory.o differ diff --git a/package.c b/package.c new file mode 100644 index 0000000..8264d7c --- /dev/null +++ b/package.c @@ -0,0 +1,234 @@ +#include +#include +#include +#include +#include +#include + +/* Package database */ +static package_t packages[PKG_MAX_PACKAGES]; +static uint32_t package_count = 0; + +/* Initialize package manager */ +void init_package_manager(void) { + /* Create package manager directories */ + fs_mkdir("/var"); + fs_mkdir("/var/amy"); + fs_mkdir("/var/amy/cache"); + fs_mkdir("/usr"); + fs_mkdir("/usr/local"); + + /* Load package database */ + pkg_db_load(); + + print_string("Package manager initialized\n"); +} + +/* Load package database */ +int pkg_db_load(void) { + int fd = fs_open(PKG_DB_PATH, FILE_MODE_READ); + if (fd < 0) { + /* Create empty database if it doesn't exist */ + fd = fs_open(PKG_DB_PATH, FILE_MODE_WRITE); + if (fd < 0) return -1; + fs_close(fd); + return 0; + } + + /* Read package count */ + fs_read(fd, &package_count, sizeof(package_count)); + + /* Read packages */ + if (package_count > PKG_MAX_PACKAGES) { + package_count = PKG_MAX_PACKAGES; + } + + for (uint32_t i = 0; i < package_count; i++) { + fs_read(fd, &packages[i], sizeof(package_t)); + } + + fs_close(fd); + return 0; +} + +/* Save package database */ +int pkg_db_save(void) { + int fd = fs_open(PKG_DB_PATH, FILE_MODE_WRITE); + if (fd < 0) return -1; + + /* Write package count */ + fs_write(fd, &package_count, sizeof(package_count)); + + /* Write packages */ + for (uint32_t i = 0; i < package_count; i++) { + fs_write(fd, &packages[i], sizeof(package_t)); + } + + fs_close(fd); + return 0; +} + +/* Find package in database */ +package_t* pkg_db_find(const char* name) { + for (uint32_t i = 0; i < package_count; i++) { + if (strcmp(packages[i].name, name) == 0) { + return &packages[i]; + } + } + return NULL; +} + +/* Package operations */ +int pkg_install(const char* name) { + package_t* pkg = pkg_db_find(name); + if (!pkg) { + /* Package not in database */ + return -1; + } + + if (pkg->state == PKG_STATE_INSTALLED) { + /* Already installed */ + return -2; + } + + /* Check dependencies */ + for (uint32_t i = 0; i < pkg->deps_count; i++) { + package_t* dep = pkg_db_find(pkg->dependencies[i]); + if (!dep || dep->state != PKG_STATE_INSTALLED) { + /* Missing dependency */ + return -3; + } + } + + /* Create package directory */ + char path[FS_MAX_PATH]; + snprintf(path, sizeof(path), "%s/%s", PKG_INSTALL_PATH, pkg->name); + fs_mkdir(path); + + /* Mark as installed */ + pkg->state = PKG_STATE_INSTALLED; + pkg_db_save(); + + return 0; +} + +int pkg_remove(const char* name) { + package_t* pkg = pkg_db_find(name); + if (!pkg) { + /* Package not in database */ + return -1; + } + + if (pkg->state != PKG_STATE_INSTALLED) { + /* Not installed */ + return -2; + } + + /* Check if other packages depend on this one */ + for (uint32_t i = 0; i < package_count; i++) { + if (packages[i].state == PKG_STATE_INSTALLED) { + for (uint32_t j = 0; j < packages[i].deps_count; j++) { + if (strcmp(packages[i].dependencies[j], name) == 0) { + /* Package is needed by another package */ + return -3; + } + } + } + } + + /* Remove package directory */ + char path[FS_MAX_PATH]; + snprintf(path, sizeof(path), "%s/%s", PKG_INSTALL_PATH, pkg->name); + fs_rmdir(path); + + /* Mark as not installed */ + pkg->state = PKG_STATE_NOT_INSTALLED; + pkg_db_save(); + + return 0; +} + +int pkg_update(const char* name) { + package_t* pkg = pkg_db_find(name); + if (!pkg) { + /* Package not in database */ + return -1; + } + + if (pkg->state != PKG_STATE_INSTALLED) { + /* Not installed */ + return -2; + } + + /* TODO: Check for newer version */ + + return 0; +} + +int pkg_list(void) { + print_string("Installed packages:\n"); + print_string("------------------\n"); + + for (uint32_t i = 0; i < package_count; i++) { + if (packages[i].state == PKG_STATE_INSTALLED) { + char buf[256]; + snprintf(buf, sizeof(buf), "%s (%s) - %s\n", + packages[i].name, + packages[i].version, + packages[i].description); + print_string(buf); + } + } + + return 0; +} + +int pkg_search(const char* query) { + print_string("Search results:\n"); + print_string("--------------\n"); + + for (uint32_t i = 0; i < package_count; i++) { + if (strstr(packages[i].name, query) || strstr(packages[i].description, query)) { + char buf[256]; + snprintf(buf, sizeof(buf), "%s (%s) [%s] - %s\n", + packages[i].name, + packages[i].version, + packages[i].state == PKG_STATE_INSTALLED ? "installed" : "not installed", + packages[i].description); + print_string(buf); + } + } + + return 0; +} + +int pkg_info(const char* name) { + package_t* pkg = pkg_db_find(name); + if (!pkg) { + /* Package not in database */ + return -1; + } + + char buf[512]; + snprintf(buf, sizeof(buf), + "Package: %s\n" + "Version: %s\n" + "Status: %s\n" + "Size: %u bytes\n" + "Description: %s\n" + "Dependencies (%u):\n", + pkg->name, + pkg->version, + pkg->state == PKG_STATE_INSTALLED ? "installed" : "not installed", + pkg->size, + pkg->description, + pkg->deps_count); + print_string(buf); + + for (uint32_t i = 0; i < pkg->deps_count; i++) { + snprintf(buf, sizeof(buf), " - %s\n", pkg->dependencies[i]); + print_string(buf); + } + + return 0; +} diff --git a/package.h b/package.h new file mode 100644 index 0000000..79517fb --- /dev/null +++ b/package.h @@ -0,0 +1,46 @@ +#ifndef _PACKAGE_H_ +#define _PACKAGE_H_ + +#include +#include + +/* Package states */ +#define PKG_STATE_NOT_INSTALLED 0 +#define PKG_STATE_INSTALLED 1 +#define PKG_STATE_UPDATING 2 + +/* Package manager constants */ +#define PKG_MAX_NAME 32 +#define PKG_MAX_VERSION 16 +#define PKG_MAX_DESC 128 +#define PKG_MAX_PACKAGES 256 +#define PKG_DB_PATH "/var/amy/packages.db" +#define PKG_CACHE_PATH "/var/amy/cache" +#define PKG_INSTALL_PATH "/usr/local" + +/* Package structure */ +typedef struct { + char name[PKG_MAX_NAME]; + char version[PKG_MAX_VERSION]; + char description[PKG_MAX_DESC]; + uint8_t state; + uint32_t size; + uint32_t deps_count; + char** dependencies; +} package_t; + +/* Package manager functions */ +void init_package_manager(void); +int pkg_install(const char* name); +int pkg_remove(const char* name); +int pkg_update(const char* name); +int pkg_list(void); +int pkg_search(const char* query); +int pkg_info(const char* name); + +/* Package database functions */ +int pkg_db_load(void); +int pkg_db_save(void); +package_t* pkg_db_find(const char* name); + +#endif /* _PACKAGE_H_ */ diff --git a/package.o b/package.o new file mode 100644 index 0000000..298831e Binary files /dev/null and b/package.o differ diff --git a/paging.asm b/paging.asm new file mode 100644 index 0000000..288b620 --- /dev/null +++ b/paging.asm @@ -0,0 +1,34 @@ +; Paging-related assembly functions +global enable_paging +global load_page_directory + +section .text + +enable_paging: + push ebp + mov ebp, esp + + ; Load CR3 with page directory address + mov eax, [ebp + 8] + mov cr3, eax + + ; Enable paging (set PG bit) + mov eax, cr0 + or eax, 0x80000000 + mov cr0, eax + + mov esp, ebp + pop ebp + ret + +load_page_directory: + push ebp + mov ebp, esp + + ; Load CR3 with page directory address + mov eax, [ebp + 8] + mov cr3, eax + + mov esp, ebp + pop ebp + ret diff --git a/paging.c b/paging.c new file mode 100644 index 0000000..320cb22 --- /dev/null +++ b/paging.c @@ -0,0 +1,73 @@ +#include +#include + +/* Page directory and first page table */ +uint32_t page_directory[1024] __attribute__((aligned(4096))); +static uint32_t first_page_table[1024] __attribute__((aligned(4096))); + +/* Assembly functions defined in paging.asm */ +extern void enable_paging(uint32_t* page_directory); +extern void load_page_directory(uint32_t* page_directory); + +void init_paging(void) { + /* Clear page directory */ + for (int i = 0; i < 1024; i++) { + page_directory[i] = 0x00000002; /* Supervisor, read/write, not present */ + } + + /* Identity map first 4MB */ + for (uint32_t i = 0; i < 1024; i++) { + first_page_table[i] = (i * PAGE_SIZE) | PAGE_PRESENT | PAGE_WRITE; + } + + /* Add first page table to directory */ + page_directory[0] = ((uint32_t)first_page_table) | PAGE_PRESENT | PAGE_WRITE; + + /* Map kernel pages */ + uint32_t kernel_pages = (KERNEL_VIRTUAL_BASE + PAGE_SIZE - 1) / PAGE_SIZE; + for (uint32_t i = 0; i < kernel_pages; i++) { + map_page((void*)(i * PAGE_SIZE), (void*)(KERNEL_VIRTUAL_BASE + i * PAGE_SIZE), PAGE_PRESENT | PAGE_WRITE); + } + + /* Load page directory and enable paging */ + load_page_directory(page_directory); + enable_paging(page_directory); + + print_string("Paging initialized\n"); +} + +void map_page(void* physical, void* virtual, uint32_t flags) { + uint32_t pd_index = (uint32_t)virtual >> 22; + uint32_t pt_index = ((uint32_t)virtual >> 12) & 0x3FF; + + /* Check if page table exists */ + if (!(page_directory[pd_index] & PAGE_PRESENT)) { + /* Create new page table */ + uint32_t* new_table = kmalloc(PAGE_SIZE); + if (!new_table) return; + + /* Clear new table */ + for (int i = 0; i < 1024; i++) { + new_table[i] = 0x00000002; + } + + /* Add to directory */ + page_directory[pd_index] = ((uint32_t)new_table) | PAGE_PRESENT | PAGE_WRITE; + } + + /* Get page table */ + uint32_t* page_table = (uint32_t*)(page_directory[pd_index] & ~0xFFF); + + /* Map the page */ + page_table[pt_index] = ((uint32_t)physical) | flags; +} + +void unmap_page(void* virtual) { + uint32_t pd_index = (uint32_t)virtual >> 22; + uint32_t pt_index = ((uint32_t)virtual >> 12) & 0x3FF; + + if (page_directory[pd_index] & PAGE_PRESENT) { + uint32_t* page_table = (uint32_t*)(page_directory[pd_index] & ~0xFFF); + page_table[pt_index] = 0x00000002; + } +} diff --git a/paging.o b/paging.o new file mode 100644 index 0000000..c4254ba Binary files /dev/null and b/paging.o differ diff --git a/pkg_commands.c b/pkg_commands.c new file mode 100644 index 0000000..be3ad10 --- /dev/null +++ b/pkg_commands.c @@ -0,0 +1,127 @@ +#include +#include + +int cmd_amy(int argc, char* argv[]) { + if (argc < 2) { + shell_puts("Usage: amy [args]\n"); + shell_puts("Commands:\n"); + shell_puts(" install - Install a package\n"); + shell_puts(" remove - Remove a package\n"); + shell_puts(" update [package] - Update packages\n"); + shell_puts(" list - List installed packages\n"); + shell_puts(" search - Search for packages\n"); + shell_puts(" info - Show package information\n"); + return 1; + } + + if (strcmp(argv[1], "install") == 0) { + if (argc != 3) { + shell_puts("Usage: amy install \n"); + return 1; + } + + int result = pkg_install(argv[2]); + switch (result) { + case 0: + shell_printf("Package '%s' installed successfully\n", argv[2]); + break; + case -1: + shell_printf("Package '%s' not found\n", argv[2]); + break; + case -2: + shell_printf("Package '%s' is already installed\n", argv[2]); + break; + case -3: + shell_printf("Failed to install dependencies for '%s'\n", argv[2]); + break; + default: + shell_printf("Failed to install package '%s'\n", argv[2]); + } + return result != 0; + } + + if (strcmp(argv[1], "remove") == 0) { + if (argc != 3) { + shell_puts("Usage: amy remove \n"); + return 1; + } + + int result = pkg_remove(argv[2]); + switch (result) { + case 0: + shell_printf("Package '%s' removed successfully\n", argv[2]); + break; + case -1: + shell_printf("Package '%s' not found\n", argv[2]); + break; + case -2: + shell_printf("Package '%s' is not installed\n", argv[2]); + break; + case -3: + shell_printf("Package '%s' is required by other packages\n", argv[2]); + break; + default: + shell_printf("Failed to remove package '%s'\n", argv[2]); + } + return result != 0; + } + + if (strcmp(argv[1], "update") == 0) { + if (argc > 3) { + shell_puts("Usage: amy update [package]\n"); + return 1; + } + + if (argc == 3) { + int result = pkg_update(argv[2]); + switch (result) { + case 0: + shell_printf("Package '%s' updated successfully\n", argv[2]); + break; + case -1: + shell_printf("Package '%s' not found\n", argv[2]); + break; + case -2: + shell_printf("Package '%s' is not installed\n", argv[2]); + break; + default: + shell_printf("Failed to update package '%s'\n", argv[2]); + } + return result != 0; + } else { + shell_puts("Updating package database...\n"); + // TODO: Implement full system update + return 0; + } + } + + if (strcmp(argv[1], "list") == 0) { + return pkg_list(); + } + + if (strcmp(argv[1], "search") == 0) { + if (argc != 3) { + shell_puts("Usage: amy search \n"); + return 1; + } + + return pkg_search(argv[2]); + } + + if (strcmp(argv[1], "info") == 0) { + if (argc != 3) { + shell_puts("Usage: amy info \n"); + return 1; + } + + int result = pkg_info(argv[2]); + if (result < 0) { + shell_printf("Package '%s' not found\n", argv[2]); + return 1; + } + return 0; + } + + shell_printf("Unknown command: %s\n", argv[1]); + return 1; +} diff --git a/process.c b/process.c new file mode 100644 index 0000000..f553380 --- /dev/null +++ b/process.c @@ -0,0 +1,139 @@ +#include +#include +#include +#include + +#define MAX_PROCESSES 64 +#define QUANTUM_MS 10 + +/* Process table */ +static process_t process_table[MAX_PROCESSES]; +static uint32_t next_pid = 1; +process_t* current_process = NULL; +static process_t* ready_queue = NULL; + +/* Initialize process manager */ +void init_process_manager(void) { + /* Clear process table */ + for (int i = 0; i < MAX_PROCESSES; i++) { + process_table[i].pid = 0; + process_table[i].state = PROCESS_STATE_TERMINATED; + } + + print_string("Process manager initialized\n"); +} + +/* Helper function to add process to ready queue */ +static void enqueue_process(process_t* process) { + if (!ready_queue) { + ready_queue = process; + process->next = NULL; + } else { + process_t* current = ready_queue; + while (current->next) { + current = current->next; + } + current->next = process; + process->next = NULL; + } +} + +/* Helper function to remove process from ready queue */ +static process_t* dequeue_process(void) { + if (!ready_queue) return NULL; + + process_t* process = ready_queue; + ready_queue = ready_queue->next; + process->next = NULL; + return process; +} + +/* Create a new process */ +process_t* create_process(const char* name, void (*entry)(void), uint32_t stack_size) { + /* Find free process slot */ + process_t* process = NULL; + for (int i = 0; i < MAX_PROCESSES; i++) { + if (process_table[i].state == PROCESS_STATE_TERMINATED) { + process = &process_table[i]; + break; + } + } + + if (!process) return NULL; + + /* Initialize process control block */ + process->pid = next_pid++; + strncpy(process->name, name, 31); + process->name[31] = '\0'; + process->state = PROCESS_STATE_READY; + + /* Allocate and set up stack */ + process->stack_size = stack_size; + process->stack = kmalloc(stack_size); + if (!process->stack) { + process->state = PROCESS_STATE_TERMINATED; + return NULL; + } + + /* Set up initial context */ + context_t* ctx = &process->context; + ctx->eip = (uint32_t)entry; + ctx->esp = (uint32_t)process->stack + stack_size - 4; + ctx->ebp = ctx->esp; + ctx->eflags = 0x200; /* Enable interrupts */ + ctx->cr3 = (uint32_t)page_directory; /* Use kernel page directory for now */ + + /* Add to ready queue */ + enqueue_process(process); + + return process; +} + +/* Terminate a process */ +void terminate_process(process_t* process) { + if (!process) return; + + /* Free resources */ + kfree(process->stack); + + /* Mark as terminated */ + process->state = PROCESS_STATE_TERMINATED; + process->pid = 0; + + /* If this is the current process, force a schedule */ + if (process == current_process) { + schedule(); + } +} + +/* Context switch assembly function (to be defined) */ +extern void context_switch(context_t* old_context, context_t* new_context); + +/* Schedule next process */ +void schedule(void) { + if (!ready_queue) return; + + /* Save current process if exists */ + if (current_process) { + if (current_process->state == PROCESS_STATE_RUNNING) { + current_process->state = PROCESS_STATE_READY; + enqueue_process(current_process); + } + } + + /* Get next process */ + process_t* next = dequeue_process(); + if (!next) return; + + /* Switch contexts */ + process_t* prev = current_process; + current_process = next; + current_process->state = PROCESS_STATE_RUNNING; + + if (prev) { + context_switch(&prev->context, &next->context); + } else { + /* First process, just load context */ + context_switch(NULL, &next->context); + } +} diff --git a/process.h b/process.h new file mode 100644 index 0000000..abc4da9 --- /dev/null +++ b/process.h @@ -0,0 +1,44 @@ +#ifndef _PROCESS_H_ +#define _PROCESS_H_ + +#include + +/* Process states */ +typedef enum { + PROCESS_STATE_READY, + PROCESS_STATE_RUNNING, + PROCESS_STATE_BLOCKED, + PROCESS_STATE_TERMINATED +} process_state_t; + +/* Process context structure */ +typedef struct { + uint32_t eax, ebx, ecx, edx; /* General purpose registers */ + uint32_t esi, edi, ebp; /* Index and base registers */ + uint32_t esp; /* Stack pointer */ + uint32_t eip; /* Instruction pointer */ + uint32_t eflags; /* CPU flags */ + uint32_t cr3; /* Page directory base */ +} context_t; + +/* Process control block */ +typedef struct process { + uint32_t pid; /* Process ID */ + char name[32]; /* Process name */ + process_state_t state; /* Current state */ + context_t context; /* CPU context */ + uint32_t* stack; /* Kernel stack */ + uint32_t stack_size; /* Stack size */ + struct process* next; /* Next process in queue */ +} process_t; + +/* Process management functions */ +void init_process_manager(void); +process_t* create_process(const char* name, void (*entry)(void), uint32_t stack_size); +void terminate_process(process_t* process); +void schedule(void); + +/* Current running process */ +extern process_t* current_process; + +#endif /* _PROCESS_H_ */ diff --git a/process.o b/process.o new file mode 100644 index 0000000..c007d5f Binary files /dev/null and b/process.o differ diff --git a/shell.c b/shell.c new file mode 100644 index 0000000..2a2fb53 --- /dev/null +++ b/shell.c @@ -0,0 +1,245 @@ +#include +#include +#include +#include +#include +#include + +#define HISTORY_SIZE 32 +#define TAB_COMPLETE_MAX 10 + +/* Command table */ +static const shell_command_t commands[] = { + {"help", cmd_help, "Display this help message"}, + {"clear", cmd_clear, "Clear the screen"}, + {"echo", cmd_echo, "Print arguments to the screen"}, + {"ps", cmd_ps, "List running processes"}, + {"kill", cmd_kill, "Terminate a process"}, + {"meminfo", cmd_meminfo, "Display memory usage information"}, + {"color", cmd_color, "Change shell color theme"}, + {"ls", cmd_ls, "List directory contents"}, + {"cd", cmd_cd, "Change current directory"}, + {"pwd", cmd_pwd, "Print working directory"}, + {"mkdir", cmd_mkdir, "Create a directory"}, + {"rmdir", cmd_rmdir, "Remove a directory"}, + {"touch", cmd_touch, "Create an empty file"}, + {"rm", cmd_rm, "Remove a file"}, + {"amy", cmd_amy, "Package manager"}, + {NULL, NULL, NULL} /* Terminator */ +}; + +/* Shell state */ +static uint8_t shell_fg_color = VGA_COLOR_LIGHT_PURPLE; +static uint8_t shell_bg_color = VGA_COLOR_BLACK; +static char history[HISTORY_SIZE][SHELL_BUFFER_SIZE]; +static int history_count = 0; +static int history_position = 0; + +/* Command line editing */ +static char current_line[SHELL_BUFFER_SIZE]; +static int cursor_position = 0; +static int line_length = 0; + +/* Find command completions */ +static int find_completions(const char* prefix, const char** completions, int max_completions) { + int count = 0; + size_t prefix_len = strlen(prefix); + + /* Check built-in commands */ + for (const shell_command_t* cmd = commands; cmd->name && count < max_completions; cmd++) { + if (strncmp(prefix, cmd->name, prefix_len) == 0) { + completions[count++] = cmd->name; + } + } + + return count; +} + +/* Clear current line */ +static void clear_line(void) { + while (cursor_position > 0) { + shell_puts("\b \b"); + cursor_position--; + } +} + +/* Redraw current line */ +static void redraw_line(void) { + clear_line(); + shell_puts(current_line); + cursor_position = line_length; +} + +/* Add command to history */ +static void add_to_history(const char* cmd) { + if (strlen(cmd) == 0) return; + + /* Don't add if same as last command */ + if (history_count > 0 && strcmp(history[history_count - 1], cmd) == 0) { + return; + } + + if (history_count < HISTORY_SIZE) { + strncpy(history[history_count], cmd, SHELL_BUFFER_SIZE - 1); + history[history_count][SHELL_BUFFER_SIZE - 1] = '\0'; + history_count++; + } else { + /* Shift history up */ + for (int i = 0; i < HISTORY_SIZE - 1; i++) { + strncpy(history[i], history[i + 1], SHELL_BUFFER_SIZE); + } + strncpy(history[HISTORY_SIZE - 1], cmd, SHELL_BUFFER_SIZE - 1); + history[HISTORY_SIZE - 1][SHELL_BUFFER_SIZE - 1] = '\0'; + } + history_position = history_count; +} + +/* Process keyboard input */ +static int process_input(char c) { + if (c == '\n') { + shell_putchar('\n'); + current_line[line_length] = '\0'; + add_to_history(current_line); + return 1; + } + + if (c == '\b') { + if (cursor_position > 0) { + memmove(¤t_line[cursor_position - 1], + ¤t_line[cursor_position], + line_length - cursor_position); + cursor_position--; + line_length--; + redraw_line(); + } + return 0; + } + + if (c == '\t') { + const char* completions[TAB_COMPLETE_MAX]; + int count = find_completions(current_line, completions, TAB_COMPLETE_MAX); + + if (count == 1) { + /* Single completion */ + strncpy(current_line, completions[0], SHELL_BUFFER_SIZE - 1); + line_length = strlen(current_line); + cursor_position = line_length; + redraw_line(); + } else if (count > 1) { + /* Show multiple completions */ + shell_putchar('\n'); + for (int i = 0; i < count; i++) { + shell_printf("%s ", completions[i]); + } + shell_putchar('\n'); + shell_puts(PROMPT); + shell_puts(current_line); + } + return 0; + } + + if (c == '\x1B') { + /* Handle arrow keys (history) */ + char seq[3]; + seq[0] = keyboard_read(); + if (seq[0] == '[') { + seq[1] = keyboard_read(); + if (seq[1] == 'A' && history_position > 0) { /* Up */ + history_position--; + strncpy(current_line, history[history_position], SHELL_BUFFER_SIZE); + line_length = strlen(current_line); + redraw_line(); + } else if (seq[1] == 'B' && history_position < history_count) { /* Down */ + history_position++; + if (history_position < history_count) { + strncpy(current_line, history[history_position], SHELL_BUFFER_SIZE); + } else { + current_line[0] = '\0'; + } + line_length = strlen(current_line); + redraw_line(); + } else if (seq[1] == 'C' && cursor_position < line_length) { /* Right */ + shell_putchar(current_line[cursor_position]); + cursor_position++; + } else if (seq[1] == 'D' && cursor_position > 0) { /* Left */ + shell_puts("\b"); + cursor_position--; + } + } + return 0; + } + + if (c >= ' ' && c <= '~' && line_length < SHELL_BUFFER_SIZE - 1) { + memmove(¤t_line[cursor_position + 1], + ¤t_line[cursor_position], + line_length - cursor_position); + current_line[cursor_position] = c; + cursor_position++; + line_length++; + redraw_line(); + } + + return 0; +} + +void init_shell(void) { + set_color(shell_fg_color, shell_bg_color); + clear_screen(); + shell_printf("AmethystOS Shell v1.0 [%s theme]\n", + shell_fg_color == VGA_COLOR_LIGHT_PURPLE ? "default" : + shell_fg_color == VGA_COLOR_PURPLE ? "dark" : "light"); + shell_puts("Type 'help' for a list of commands\n\n"); +} + +void shell_main(void) { + while (1) { + set_color(shell_fg_color, shell_bg_color); + shell_puts(PROMPT); + + /* Reset line state */ + line_length = 0; + cursor_position = 0; + current_line[0] = '\0'; + + /* Read command */ + while (1) { + char c = keyboard_read(); + if (process_input(c)) break; + } + + /* Process command */ + shell_process_command(current_line); + } +} + +void shell_process_command(char* cmd) { + char* argv[MAX_ARGS]; + int argc = 0; + + /* Skip leading whitespace */ + while (*cmd && *cmd <= ' ') cmd++; + + /* Parse command line */ + char* token = strtok(cmd, " "); + while (token && argc < MAX_ARGS) { + argv[argc++] = token; + token = strtok(NULL, " "); + } + + if (argc == 0) return; + + /* Search for command */ + for (const shell_command_t* cmd = commands; cmd->name; cmd++) { + if (strcmp(argv[0], cmd->name) == 0) { + int result = cmd->func(argc, argv); + if (result != 0) { + shell_printf("Command failed with error code: %d\n", result); + } + return; + } + } + + /* Command not found */ + shell_printf("Unknown command: %s\n", argv[0]); + shell_puts("Type 'help' for a list of available commands\n"); +} diff --git a/shell.h b/shell.h new file mode 100644 index 0000000..a6e5ae3 --- /dev/null +++ b/shell.h @@ -0,0 +1,52 @@ +#ifndef _SHELL_H_ +#define _SHELL_H_ + +#include + +#define SHELL_BUFFER_SIZE 256 +#define MAX_ARGS 16 +#define PROMPT "amethyst> " +#define HISTORY_SIZE 32 +#define ESC_SEQ_MAX_LEN 4 + +/* Command function type */ +typedef int (*command_func_t)(int argc, char* argv[]); + +/* Command structure */ +typedef struct { + const char* name; + command_func_t func; + const char* help; +} shell_command_t; + +/* Shell functions */ +void init_shell(void); +void shell_main(void); +void shell_process_command(char* cmd); + +/* Input/Output functions */ +void shell_puts(const char* str); +void shell_printf(const char* format, ...); +char shell_getchar(void); +void shell_readline(char* buffer, size_t size); + +/* Built-in commands */ +int cmd_help(int argc, char* argv[]); +int cmd_clear(int argc, char* argv[]); +int cmd_echo(int argc, char* argv[]); +int cmd_ps(int argc, char* argv[]); +int cmd_kill(int argc, char* argv[]); +int cmd_meminfo(int argc, char* argv[]); +int cmd_color(int argc, char* argv[]); +int cmd_amy(int argc, char* argv[]); /* Package manager */ + +/* File system commands */ +int cmd_ls(int argc, char* argv[]); +int cmd_cd(int argc, char* argv[]); +int cmd_pwd(int argc, char* argv[]); +int cmd_mkdir(int argc, char* argv[]); +int cmd_rmdir(int argc, char* argv[]); +int cmd_touch(int argc, char* argv[]); +int cmd_rm(int argc, char* argv[]); + +#endif /* _SHELL_H_ */ diff --git a/shell_commands.c b/shell_commands.c new file mode 100644 index 0000000..e69de29 diff --git a/shell_io.c b/shell_io.c new file mode 100644 index 0000000..2d3ef9d --- /dev/null +++ b/shell_io.c @@ -0,0 +1,181 @@ +#include +#include +#include + +#define MAX_PRINTF_BUF 256 + +void shell_puts(const char* str) { + print_string(str); +} + +void shell_putchar(char c) { + char str[2] = {c, '\0'}; + print_string(str); +} + +void shell_printf(const char* format, ...) { + char buffer[MAX_PRINTF_BUF]; + va_list args; + va_start(args, format); + + char* buf_ptr = buffer; + while (*format && (buf_ptr - buffer) < MAX_PRINTF_BUF - 1) { + if (*format == '%') { + format++; + switch (*format) { + case 'd': { + int val = va_arg(args, int); + int tmp = val; + char num_buf[12]; + char* num_ptr = num_buf + 11; + *num_ptr = '\0'; + + if (val < 0) { + val = -val; + *buf_ptr++ = '-'; + } + + do { + *--num_ptr = '0' + (val % 10); + val /= 10; + } while (val); + + while (*num_ptr) { + *buf_ptr++ = *num_ptr++; + } + break; + } + case 's': { + char* str = va_arg(args, char*); + while (*str) { + *buf_ptr++ = *str++; + } + break; + } + default: + *buf_ptr++ = *format; + break; + } + } else { + *buf_ptr++ = *format; + } + format++; + } + + *buf_ptr = '\0'; + va_end(args); + + print_string(buffer); +} + +char shell_getchar(void) { + // TODO: Implement keyboard driver and input handling + return '\0'; +} + +/* Command history */ +static char history[HISTORY_SIZE][SHELL_BUFFER_SIZE]; +static int history_count = 0; +static int history_position = 0; + +/* Clear the current line in the terminal */ +static void clear_line(size_t current_pos) { + while (current_pos > 0) { + shell_puts("\b \b"); + current_pos--; + } +} + +/* Handle escape sequence */ +static int handle_escape_sequence(char* seq) { + if (strcmp(seq, "[A") == 0) { /* Up arrow */ + return 1; + } else if (strcmp(seq, "[B") == 0) { /* Down arrow */ + return 2; + } + return 0; +} + +void shell_readline(char* buffer, size_t size) { + size_t i = 0; + char c; + char esc_seq[ESC_SEQ_MAX_LEN] = {0}; + int esc_pos = 0; + int in_escape = 0; + int temp_history_pos = history_position; + + /* Clear the buffer */ + memset(buffer, 0, size); + + while (i < size - 1) { + c = keyboard_read(); + if (!c) continue; + + if (c == '\x1B') { /* Escape sequence start */ + in_escape = 1; + esc_pos = 0; + continue; + } + + if (in_escape) { + if (esc_pos < ESC_SEQ_MAX_LEN - 1) { + esc_seq[esc_pos++] = c; + esc_seq[esc_pos] = '\0'; + + int action = handle_escape_sequence(esc_seq); + if (action > 0) { + /* Handle up/down arrows */ + if (action == 1 && temp_history_pos > 0) { /* Up arrow */ + temp_history_pos--; + clear_line(i); + strncpy(buffer, history[temp_history_pos], size - 1); + i = strlen(buffer); + shell_puts(buffer); + } else if (action == 2 && temp_history_pos < history_count) { /* Down arrow */ + temp_history_pos++; + clear_line(i); + if (temp_history_pos < history_count) { + strncpy(buffer, history[temp_history_pos], size - 1); + i = strlen(buffer); + shell_puts(buffer); + } else { + buffer[0] = '\0'; + i = 0; + } + } + in_escape = 0; + } + } + continue; + } + + if (c == '\n') { + shell_putchar('\n'); + /* Add to history if not empty and different from last command */ + if (i > 0 && (history_count == 0 || strcmp(buffer, history[history_count - 1]) != 0)) { + if (history_count < HISTORY_SIZE) { + strncpy(history[history_count], buffer, SHELL_BUFFER_SIZE - 1); + history_count++; + } else { + /* Shift history up */ + for (int j = 0; j < HISTORY_SIZE - 1; j++) { + strncpy(history[j], history[j + 1], SHELL_BUFFER_SIZE - 1); + } + strncpy(history[HISTORY_SIZE - 1], buffer, SHELL_BUFFER_SIZE - 1); + } + history_position = history_count; + } + break; + } else if (c == '\b') { + if (i > 0) { + i--; + shell_puts("\b \b"); /* Erase character */ + } + } else { + buffer[i++] = c; + shell_putchar(c); + } + } + + buffer[i] = '\0'; +} diff --git a/string.c b/string.c new file mode 100644 index 0000000..2157fc1 --- /dev/null +++ b/string.c @@ -0,0 +1,240 @@ +#include +#include +#include + +size_t strlen(const char* str) { + size_t len = 0; + while (str[len]) len++; + return len; +} + +char* strcpy(char* dest, const char* src) { + size_t i = 0; + while ((dest[i] = src[i])) i++; + return dest; +} + +char* strncpy(char* dest, const char* src, size_t n) { + size_t i; + for (i = 0; i < n && src[i]; i++) { + dest[i] = src[i]; + } + for (; i < n; i++) { + dest[i] = '\0'; + } + return dest; +} + +int strcmp(const char* s1, const char* s2) { + while (*s1 && *s1 == *s2) { + s1++; + s2++; + } + return (unsigned char)*s1 - (unsigned char)*s2; +} + +int strncmp(const char* s1, const char* s2, size_t n) { + while (n-- > 0) { + if (*s1 != *s2) { + return (unsigned char)*s1 - (unsigned char)*s2; + } + if (*s1 == '\0') { + return 0; + } + s1++; + s2++; + } + return 0; +} + +void* memset(void* s, int c, size_t n) { + unsigned char* p = s; + while (n--) { + *p++ = (unsigned char)c; + } + return s; +} + +void* memcpy(void* dest, const void* src, size_t n) { + unsigned char* d = dest; + const unsigned char* s = src; + while (n--) { + *d++ = *s++; + } + return dest; +} + +void* memmove(void* dest, const void* src, size_t n) { + unsigned char* d = dest; + const unsigned char* s = src; + if (d < s) { + while (n--) { + *d++ = *s++; + } + } else { + d += n; + s += n; + while (n--) { + *--d = *--s; + } + } + return dest; +} + +/* Simple printf-like formatting */ +int vsnprintf(char* str, size_t size, const char* format, va_list ap) { + if (size == 0) return 0; + + size_t pos = 0; + while (*format && pos < size - 1) { + if (*format != '%') { + str[pos++] = *format++; + continue; + } + + format++; /* Skip % */ + + /* Handle format specifiers */ + switch (*format) { + case 's': { + const char* s = va_arg(ap, const char*); + while (*s && pos < size - 1) { + str[pos++] = *s++; + } + break; + } + case 'd': { + int val = va_arg(ap, int); + if (val < 0) { + str[pos++] = '-'; + val = -val; + } + /* Convert to string */ + char num[32]; + int i = 0; + do { + num[i++] = val % 10 + '0'; + val /= 10; + } while (val && i < 31); + /* Copy in reverse */ + while (--i >= 0 && pos < size - 1) { + str[pos++] = num[i]; + } + break; + } + case 'x': { + unsigned int val = va_arg(ap, unsigned int); + /* Convert to hex string */ + char num[32]; + int i = 0; + do { + int digit = val & 0xF; + num[i++] = digit < 10 ? digit + '0' : digit - 10 + 'a'; + val >>= 4; + } while (val && i < 31); + /* Copy in reverse */ + while (--i >= 0 && pos < size - 1) { + str[pos++] = num[i]; + } + break; + } + case '%': + if (pos < size - 1) { + str[pos++] = '%'; + } + break; + default: + /* Ignore invalid format specifiers */ + break; + } + format++; + } + + str[pos] = '\0'; + return pos; +} + +int snprintf(char* str, size_t size, const char* format, ...) { + va_list ap; + va_start(ap, format); + int result = vsnprintf(str, size, format, ap); + va_end(ap); + return result; +} + +/* strtok static state */ +static char* strtok_next = NULL; + +char* strtok(char* str, const char* delim) { + char* token_start; + + /* If string is provided, start fresh */ + if (str) { + token_start = str; + } else { + /* Otherwise continue from last position */ + if (!strtok_next) return NULL; + token_start = strtok_next; + } + + /* Skip leading delimiters */ + while (*token_start && strchr(delim, *token_start)) { + token_start++; + } + + /* If we hit the end, no more tokens */ + if (!*token_start) { + strtok_next = NULL; + return NULL; + } + + /* Find end of token */ + char* token_end = token_start; + while (*token_end && !strchr(delim, *token_end)) { + token_end++; + } + + /* If we hit a delimiter, null terminate and save next position */ + if (*token_end) { + *token_end = '\0'; + strtok_next = token_end + 1; + } else { + strtok_next = NULL; + } + + return token_start; +} + +char* strchr(const char* s, int c) { + while (*s) { + if (*s == (char)c) { + return (char*)s; + } + s++; + } + return NULL; +} + +char* strrchr(const char* s, int c) { + const char* last = NULL; + while (*s) { + if (*s == (char)c) { + last = s; + } + s++; + } + return (char*)last; +} + +char* strstr(const char* haystack, const char* needle) { + size_t needle_len = strlen(needle); + if (!needle_len) return (char*)haystack; + + while (*haystack) { + if (strncmp(haystack, needle, needle_len) == 0) { + return (char*)haystack; + } + haystack++; + } + return NULL; +} diff --git a/string.h b/string.h new file mode 100644 index 0000000..b075ac3 --- /dev/null +++ b/string.h @@ -0,0 +1,25 @@ +#ifndef _AMETHYST_STRING_H_ +#define _AMETHYST_STRING_H_ + +#include +#include + +/* String functions */ +size_t strlen(const char* str); +char* strcpy(char* dest, const char* src); +char* strncpy(char* dest, const char* src, size_t n); +int strcmp(const char* s1, const char* s2); +int strncmp(const char* s1, const char* s2, size_t n); +char* strtok(char* str, const char* delim); +char* strrchr(const char* s, int c); +char* strchr(const char* s, int c); +char* strstr(const char* haystack, const char* needle); + +/* Memory functions */ +void* memset(void* s, int c, size_t n); +void* memcpy(void* dest, const void* src, size_t n); +void* memmove(void* dest, const void* src, size_t n); +int snprintf(char* str, size_t size, const char* format, ...); +int vsnprintf(char* str, size_t size, const char* format, va_list ap); + +#endif /* _AMETHYST_STRING_H_ */ diff --git a/string.o b/string.o new file mode 100644 index 0000000..c64a08d Binary files /dev/null and b/string.o differ diff --git a/video.c b/video.c new file mode 100644 index 0000000..9c4e76f --- /dev/null +++ b/video.c @@ -0,0 +1,61 @@ +#include + +static uint16_t* const VGA_MEMORY = (uint16_t*)0xB8000; +static uint8_t current_color = 0; +static size_t cursor_x = 0; +static size_t cursor_y = 0; + +static uint8_t make_color(uint8_t fg, uint8_t bg) { + return fg | (bg << 4); +} + +static uint16_t make_vga_entry(char c, uint8_t color) { + return (uint16_t)c | ((uint16_t)color << 8); +} + +void init_video(void) { + current_color = make_color(VGA_COLOR_LIGHT_PURPLE, VGA_COLOR_BLACK); + clear_screen(); +} + +void clear_screen(void) { + for (size_t y = 0; y < VGA_HEIGHT; y++) { + for (size_t x = 0; x < VGA_WIDTH; x++) { + const size_t index = y * VGA_WIDTH + x; + VGA_MEMORY[index] = make_vga_entry(' ', current_color); + } + } + cursor_x = 0; + cursor_y = 0; +} + +void print_string(const char* str) { + for (size_t i = 0; str[i] != '\0'; i++) { + if (str[i] == '\n') { + cursor_x = 0; + cursor_y++; + if (cursor_y >= VGA_HEIGHT) { + // TODO: Implement scrolling + cursor_y = 0; + } + continue; + } + + const size_t index = cursor_y * VGA_WIDTH + cursor_x; + VGA_MEMORY[index] = make_vga_entry(str[i], current_color); + + cursor_x++; + if (cursor_x >= VGA_WIDTH) { + cursor_x = 0; + cursor_y++; + if (cursor_y >= VGA_HEIGHT) { + // TODO: Implement scrolling + cursor_y = 0; + } + } + } +} + +void set_color(uint8_t fg, uint8_t bg) { + current_color = make_color(fg, bg); +} diff --git a/video.h b/video.h new file mode 100644 index 0000000..af15b8b --- /dev/null +++ b/video.h @@ -0,0 +1,22 @@ +#ifndef _VIDEO_H_ +#define _VIDEO_H_ + +#include + +/* VGA text mode color constants */ +#define VGA_COLOR_BLACK 0 +#define VGA_COLOR_PURPLE 5 +#define VGA_COLOR_LIGHT_PURPLE 13 +#define VGA_COLOR_WHITE 15 + +/* Screen dimensions */ +#define VGA_WIDTH 80 +#define VGA_HEIGHT 25 + +/* Function prototypes */ +void init_video(void); +void clear_screen(void); +void print_string(const char* str); +void set_color(uint8_t fg, uint8_t bg); + +#endif /* _VIDEO_H_ */