commit 1266a65b82523741f93f5cbfcffcd2cf38c632d7 Author: Pavel Kalaš (Floxen) Date: Sun Jul 12 05:44:42 2026 +0200 Pridana verze v0.1 diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..6045f8b --- /dev/null +++ b/.gitattributes @@ -0,0 +1,2 @@ +*.sh text eol=lf +setup text eol=lf diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..3e3152a --- /dev/null +++ b/.gitignore @@ -0,0 +1,11 @@ +build/ +tools/ +asterfs.img +clean.sh +CONTRIBUTING.md +DOCS.md +README.md +ROADMAP.md +setup +setup.sh +setup-runfromdisk.sh diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..32ed549 --- /dev/null +++ b/Makefile @@ -0,0 +1,84 @@ +# AsterOS build systém + +ARCH := x86_64 +# Prefer dedicated cross toolchain, fallback to host toolchain when unavailable. +CROSS ?= $(shell if command -v x86_64-elf-gcc >/dev/null 2>&1; then echo x86_64-elf-; else echo; fi) +CC := $(CROSS)gcc +LD := $(CROSS)ld +OBJCOPY := $(CROSS)objcopy +AS := nasm + +CFLAGS := -std=gnu11 -ffreestanding -fno-stack-protector -fno-pic -m64 -Wall -Wextra -Iinclude +LDFLAGS := -nostdlib -T linker.ld + +BUILD := build +IMG := $(BUILD)/aster.img +DATA_DISK := asterfs.img +BRIDGE_IF ?= br0 + +KERNEL_OBJS := \ + $(BUILD)/arch/x86_64/entry.o \ + $(BUILD)/arch/x86_64/gdt.o \ + $(BUILD)/arch/x86_64/idt.o \ + $(BUILD)/arch/x86_64/interrupts.o \ + $(BUILD)/arch/x86_64/cpu.o \ + $(BUILD)/kernel/main.o \ + $(BUILD)/kernel/panic.o \ + $(BUILD)/kernel/printk.o \ + $(BUILD)/kernel/memory.o \ + $(BUILD)/kernel/process.o \ + $(BUILD)/kernel/scheduler.o \ + $(BUILD)/kernel/syscall.o \ + $(BUILD)/kernel/aster_api.o \ + $(BUILD)/kernel/string.o \ + $(BUILD)/drivers/display.o \ + $(BUILD)/drivers/keyboard.o \ + $(BUILD)/drivers/timer.o \ + $(BUILD)/drivers/storage.o + +all: $(IMG) + +$(BUILD): + mkdir -p $(BUILD)/boot $(BUILD)/kernel $(BUILD)/arch/x86_64 $(BUILD)/drivers + +$(BUILD)/boot/boot.bin: boot/boot.asm | $(BUILD) + $(AS) -f bin $< -o $@ + +$(BUILD)/boot/stage2.bin: boot/stage2.asm boot/disk.asm | $(BUILD) + $(AS) -I boot/ -f bin $< -o $@ + +$(BUILD)/arch/x86_64/%.o: arch/x86_64/%.asm | $(BUILD) + $(AS) -f elf64 $< -o $@ + +$(BUILD)/arch/x86_64/%.o: arch/x86_64/%.c | $(BUILD) + $(CC) $(CFLAGS) -c $< -o $@ + +$(BUILD)/kernel/%.o: kernel/%.c | $(BUILD) + $(CC) $(CFLAGS) -c $< -o $@ + +$(BUILD)/drivers/%.o: drivers/%.c | $(BUILD) + $(CC) $(CFLAGS) -c $< -o $@ + +$(BUILD)/kernel.elf: $(KERNEL_OBJS) linker.ld + $(LD) $(LDFLAGS) -o $@ $(KERNEL_OBJS) + +$(BUILD)/kernel.bin: $(BUILD)/kernel.elf + $(OBJCOPY) -O binary $< $@ + +$(IMG): $(BUILD)/boot/boot.bin $(BUILD)/boot/stage2.bin $(BUILD)/kernel.bin + dd if=/dev/zero of=$(IMG) bs=512 count=2880 + dd if=$(BUILD)/boot/boot.bin of=$(IMG) conv=notrunc + dd if=$(BUILD)/boot/stage2.bin of=$(IMG) bs=512 seek=1 conv=notrunc + dd if=$(BUILD)/kernel.bin of=$(IMG) bs=512 seek=17 conv=notrunc + +$(DATA_DISK): + if [ -f $(BUILD)/asterfs.img ] && [ ! -f $(DATA_DISK) ]; then cp $(BUILD)/asterfs.img $(DATA_DISK); fi + test -f $(DATA_DISK) || dd if=/dev/zero of=$(DATA_DISK) bs=512 count=8192 + +run: $(IMG) $(DATA_DISK) + qemu-system-x86_64 -no-reboot -no-shutdown -drive format=raw,file=$(IMG),if=ide,index=0 -drive format=raw,file=$(DATA_DISK),if=ide,index=1 + +clean: + rm -rf $(BUILD) + +.PHONY: all run clean diff --git a/apps/calc_app.c b/apps/calc_app.c new file mode 100644 index 0000000..aff66a0 --- /dev/null +++ b/apps/calc_app.c @@ -0,0 +1,71 @@ +/* + * AsterOS Kernel + * Autor: Pavel Kalas + * Rok: 2026 + * + */ + +/* + * Ukazkova C aplikace nad Aster API. Aplikace predvadi jednoduchou + * kalkulacku a muze slouzit jako sablona pro dalsi user-space programy. + */ + +#include "aster_api.h" + +static void append_number(char *out, int value) { + char tmp[16]; + int i = 0; + int j; + int neg = 0; + + if (value == 0) { + out[0] = '0'; + out[1] = '\0'; + return; + } + + if (value < 0) { + neg = 1; + value = -value; + } + + while (value > 0 && i < 15) { + tmp[i++] = (char)('0' + (value % 10)); + value /= 10; + } + + j = 0; + if (neg) { + out[j++] = '-'; + } + + while (i > 0) { + out[j++] = tmp[--i]; + } + + out[j] = '\0'; +} + +void app_calc_main(void) { + char n1[16]; + char n2[16]; + char nr[16]; + char line[80]; + int a = 24; + int b = 18; + int r = a + b; + + append_number(n1, a); + append_number(n2, b); + append_number(nr, r); + + line[0] = '\0'; + aster_api_print("[calc_app] start\n"); + aster_api_print("[calc_app] "); + aster_api_print(n1); + aster_api_print(" + "); + aster_api_print(n2); + aster_api_print(" = "); + aster_api_print(nr); + aster_api_print("\n"); +} diff --git a/arch/x86_64/cpu.c b/arch/x86_64/cpu.c new file mode 100644 index 0000000..3ee6ccd --- /dev/null +++ b/arch/x86_64/cpu.c @@ -0,0 +1,161 @@ +/* + * AsterOS Kernel + * Autor: Pavel Kalaš + * Rok: 2026 + * + */ + +/* + * Tento architekturalni modul inicializuje GDT, IDT a remapuje PIC. + * Obsahuje centralni vstup pro obsluhu vyjimek, hardwarovych preruseni + * i syscall vektoru, vcetne predani rizeni scheduleru pri timer IRQ. + */ + +#include "cpu.h" +#include "panic.h" +#include "printk.h" +#include "scheduler.h" +#include "syscall.h" +#include "timer.h" +#include "types.h" + +extern void arch_load_gdt(void *ptr); +extern void arch_reload_segments(void); +extern void arch_load_idt(void *ptr); +extern void *isr_stub_table[]; + +#define IDT_ENTRIES 256 + +static inline void outb(u16 port, u8 value) { + __asm__ volatile ("outb %0, %1" : : "a"(value), "Nd"(port)); +} + +static inline void io_wait(void) { + __asm__ volatile ("outb %%al, $0x80" : : "a"(0)); +} + +struct __attribute__((packed)) gdt_ptr { + u16 limit; + u64 base; +}; + +struct __attribute__((packed)) idt_entry { + u16 offset_low; + u16 selector; + u8 ist; + u8 type_attr; + u16 offset_mid; + u32 offset_high; + u32 zero; +}; + +struct __attribute__((packed)) idt_ptr { + u16 limit; + u64 base; +}; + +static u64 gdt_table[3]; +static struct idt_entry idt[IDT_ENTRIES]; + +static void set_idt_gate(u8 vec, void *isr, u8 flags) { + u64 addr = (u64)isr; + idt[vec].offset_low = (u16)(addr & 0xFFFF); + idt[vec].selector = 0x08; + idt[vec].ist = 0; + idt[vec].type_attr = flags; + idt[vec].offset_mid = (u16)((addr >> 16) & 0xFFFF); + idt[vec].offset_high = (u32)((addr >> 32) & 0xFFFFFFFF); + idt[vec].zero = 0; +} + +static void pic_remap(void) { + outb(0x20, 0x11); + io_wait(); + outb(0xA0, 0x11); + io_wait(); + + outb(0x21, 0x20); + io_wait(); + outb(0xA1, 0x28); + io_wait(); + + outb(0x21, 0x04); + io_wait(); + outb(0xA1, 0x02); + io_wait(); + + outb(0x21, 0x01); + io_wait(); + outb(0xA1, 0x01); + io_wait(); + + outb(0x21, 0x00); + outb(0xA1, 0x00); +} + +void cpu_init(void) { + struct gdt_ptr gp; + + gdt_table[0] = 0x0000000000000000ULL; + gdt_table[1] = 0x00AF9A000000FFFFULL; + gdt_table[2] = 0x00CF92000000FFFFULL; + + gp.limit = sizeof(gdt_table) - 1; + gp.base = (u64)&gdt_table[0]; + + arch_load_gdt(&gp); + arch_reload_segments(); +} + +void interrupts_init(void) { + struct idt_ptr ip; + u32 i; + + for (i = 0; i < IDT_ENTRIES; ++i) { + set_idt_gate((u8)i, isr_stub_table[0], 0x8E); + } + + for (i = 0; i < 34; ++i) { + set_idt_gate((u8)i, isr_stub_table[i], 0x8E); + } + + set_idt_gate(128, isr_stub_table[34], 0xEE); + + ip.limit = sizeof(idt) - 1; + ip.base = (u64)&idt[0]; + + arch_load_idt(&ip); + pic_remap(); + + __asm__ volatile ("sti"); +} + +void interrupt_dispatch(interrupt_frame_t *frame) { + if (frame->vector < 32) { + printk("[EXC] vector=%d error=%x\n", (int)frame->vector, (u32)frame->error); + panic("CPU exception"); + } + + if (frame->vector == 32) { + scheduler_tick(); + outb(0x20, 0x20); + return; + } + + if (frame->vector == 33) { + outb(0x20, 0x20); + return; + } + + if (frame->vector == 128) { + frame->rax = syscall_dispatch(frame->rax, frame->rbx, frame->rcx, frame->rdx, frame->rsi); + return; + } +} + +void cpu_halt(void) { + __asm__ volatile ("cli"); + for (;;) { + __asm__ volatile ("hlt"); + } +} diff --git a/arch/x86_64/entry.asm b/arch/x86_64/entry.asm new file mode 100644 index 0000000..2658262 --- /dev/null +++ b/arch/x86_64/entry.asm @@ -0,0 +1,29 @@ +; /* +; * AsterOS Kernel +; * Autor: Pavel Kalaš +; * Rok: 2026 +; * +; * Vytvořeno jen tak z nudy a z chuti zkoušet nové věci. +; */ + +[bits 64] + +section .text.boot +global _start +extern kmain + +_start: + cli + mov word [0xB800A], 0x0F4B + mov rsp, stack_top + call kmain + +.halt: + hlt + jmp .halt + +section .bss +align 16 +stack_bottom: + resb 16384 +stack_top: diff --git a/arch/x86_64/gdt.asm b/arch/x86_64/gdt.asm new file mode 100644 index 0000000..925a09f --- /dev/null +++ b/arch/x86_64/gdt.asm @@ -0,0 +1,31 @@ +; /* +; * AsterOS Kernel +; * Autor: Pavel Kalaš +; * Rok: 2026 +; * +; * Vytvořeno jen tak z nudy a z chuti zkoušet nové věci. +; */ + +[bits 64] +section .text +global arch_load_gdt +global arch_reload_segments + +arch_load_gdt: + lgdt [rdi] + ret + +arch_reload_segments: + mov ax, 0x10 + mov ds, ax + mov es, ax + mov fs, ax + mov gs, ax + mov ss, ax + + push qword 0x08 + lea rax, [rel .flush] + push rax + retfq +.flush: + ret diff --git a/arch/x86_64/idt.asm b/arch/x86_64/idt.asm new file mode 100644 index 0000000..530b06d --- /dev/null +++ b/arch/x86_64/idt.asm @@ -0,0 +1,15 @@ +; /* +; * AsterOS Kernel +; * Autor: Pavel Kalaš +; * Rok: 2026 +; * +; * Vytvořeno jen tak z nudy a z chuti zkoušet nové věci. +; */ + +[bits 64] +section .text +global arch_load_idt + +arch_load_idt: + lidt [rdi] + ret diff --git a/arch/x86_64/interrupts.asm b/arch/x86_64/interrupts.asm new file mode 100644 index 0000000..33a19c4 --- /dev/null +++ b/arch/x86_64/interrupts.asm @@ -0,0 +1,143 @@ +; /* +; * AsterOS Kernel +; * Autor: Pavel Kalaš +; * Rok: 2026 +; * +; * Vytvořeno jen tak z nudy a z chuti zkoušet nové věci. +; */ + +[bits 64] +section .text + +extern interrupt_dispatch + +global isr_stub_table + +%macro ISR_NOERR 1 +global isr_%1 +isr_%1: + push qword 0 + push qword %1 + jmp isr_common +%endmacro + +%macro ISR_ERR 1 +global isr_%1 +isr_%1: + push qword %1 + jmp isr_common +%endmacro + +ISR_NOERR 0 +ISR_NOERR 1 +ISR_NOERR 2 +ISR_NOERR 3 +ISR_NOERR 4 +ISR_NOERR 5 +ISR_NOERR 6 +ISR_NOERR 7 +ISR_ERR 8 +ISR_NOERR 9 +ISR_ERR 10 +ISR_ERR 11 +ISR_ERR 12 +ISR_ERR 13 +ISR_ERR 14 +ISR_NOERR 15 +ISR_NOERR 16 +ISR_ERR 17 +ISR_NOERR 18 +ISR_NOERR 19 +ISR_NOERR 20 +ISR_ERR 21 +ISR_NOERR 22 +ISR_NOERR 23 +ISR_NOERR 24 +ISR_NOERR 25 +ISR_NOERR 26 +ISR_NOERR 27 +ISR_NOERR 28 +ISR_NOERR 29 +ISR_ERR 30 +ISR_NOERR 31 +ISR_NOERR 32 +ISR_NOERR 33 +ISR_NOERR 128 + +isr_common: + cld + push rax + push rbx + push rcx + push rdx + push rbp + push rdi + push rsi + push r8 + push r9 + push r10 + push r11 + push r12 + push r13 + push r14 + push r15 + + mov rdi, rsp + call interrupt_dispatch + + pop r15 + pop r14 + pop r13 + pop r12 + pop r11 + pop r10 + pop r9 + pop r8 + pop rsi + pop rdi + pop rbp + pop rdx + pop rcx + pop rbx + pop rax + + add rsp, 16 + iretq + +section .rodata +isr_stub_table: + dq isr_0 + dq isr_1 + dq isr_2 + dq isr_3 + dq isr_4 + dq isr_5 + dq isr_6 + dq isr_7 + dq isr_8 + dq isr_9 + dq isr_10 + dq isr_11 + dq isr_12 + dq isr_13 + dq isr_14 + dq isr_15 + dq isr_16 + dq isr_17 + dq isr_18 + dq isr_19 + dq isr_20 + dq isr_21 + dq isr_22 + dq isr_23 + dq isr_24 + dq isr_25 + dq isr_26 + dq isr_27 + dq isr_28 + dq isr_29 + dq isr_30 + dq isr_31 + dq isr_32 + dq isr_33 + dq isr_128 diff --git a/boot/boot.asm b/boot/boot.asm new file mode 100644 index 0000000..72ef44a --- /dev/null +++ b/boot/boot.asm @@ -0,0 +1,74 @@ +; /* +; * AsterOS Kernel +; * Autor: Pavel Kalaš +; * Rok: 2026 +; * +; */ + +[org 0x7C00] +[bits 16] + +STAGE2_SECTORS equ 16 +STAGE2_LBA equ 1 + +start: + cli + xor ax, ax + mov ds, ax + mov es, ax + mov ss, ax + mov sp, 0x7C00 + sti + + mov [boot_drive], dl + + mov si, boot_msg + call print_string + + mov word [dap + 2], STAGE2_SECTORS + mov word [dap + 4], 0x8000 + mov word [dap + 6], 0x0000 + mov dword [dap + 8], STAGE2_LBA + mov dword [dap + 12], 0 + + mov si, dap + mov ah, 0x42 + mov dl, [boot_drive] + int 0x13 + jc disk_error + + jmp 0x0000:0x8000 + +disk_error: + mov si, disk_msg + call print_string + jmp $ + +print_string: + lodsb + test al, al + jz .done + mov ah, 0x0E + mov bh, 0x00 + mov bl, 0x0F + int 0x10 + jmp print_string +.done: + ret + +boot_drive: db 0 +boot_msg: db "Aster Bootloader stage1", 13, 10, 0 +disk_msg: db "Disk read error", 13, 10, 0 + +align 8 +dap: + db 0x10 + db 0 + dw 0 + dw 0 + dw 0 + dd 0 + dd 0 + +times 510 - ($ - $$) db 0 +dw 0xAA55 diff --git a/boot/disk.asm b/boot/disk.asm new file mode 100644 index 0000000..5140506 --- /dev/null +++ b/boot/disk.asm @@ -0,0 +1,18 @@ +; /* +; * AsterOS Kernel +; * Autor: Pavel Kalaš +; * Rok: 2026 +; * +; */ + +[bits 16] + +global disk_read_lba + +; DS:SI -> adresa DAP struktury, DL -> číslo disku +disk_read_lba: + push ax + mov ah, 0x42 + int 0x13 + pop ax + ret diff --git a/boot/stage2.asm b/boot/stage2.asm new file mode 100644 index 0000000..71a838d --- /dev/null +++ b/boot/stage2.asm @@ -0,0 +1,245 @@ +; /* +; * AsterOS Kernel +; * Autor: Pavel Kalaš +; * Rok: 2026 +; * +; */ + +[org 0x8000] +[bits 16] + +jmp start2 + +%include "disk.asm" + +KERNEL_SECTORS equ 256 +KERNEL_START_LBA equ 17 +KERNEL_LOAD_SEG equ 0x1000 +PML4_ADDR equ 0x00070000 +PDPT_ADDR equ 0x00071000 +PD_ADDR equ 0x00072000 + +start2: + cli + xor ax, ax + mov ds, ax + mov es, ax + mov ss, ax + mov sp, 0x9000 + sti + + mov [boot_drive], dl + + mov si, msg_stage2 + call print_string + + call enable_a20 + call load_kernel + jc load_error + + mov si, msg_kernel_loaded + call print_string + + cli + lgdt [gdt_descriptor] + mov eax, cr0 + or eax, 0x1 + mov cr0, eax + jmp 0x08:protected_entry + +load_error: + mov si, msg_load_error + call print_string + jmp $ + +enable_a20: + in al, 0x92 + or al, 0x02 + out 0x92, al + ret + +load_kernel: + mov word [remaining_sectors], KERNEL_SECTORS + mov word [current_seg], KERNEL_LOAD_SEG + mov dword [current_lba], KERNEL_START_LBA + +.next_chunk: + mov ax, [remaining_sectors] + test ax, ax + jz .done + + cmp ax, 127 + jbe .use_rest + mov ax, 127 + +.use_rest: + mov [chunk_sectors], ax + + mov word [dap + 2], ax + mov word [dap + 4], 0x0000 + mov ax, [current_seg] + mov word [dap + 6], ax + mov eax, [current_lba] + mov dword [dap + 8], eax + mov dword [dap + 12], 0 + + mov dl, [boot_drive] + mov si, dap + call disk_read_lba + jc .error + + mov ax, [chunk_sectors] + shl ax, 5 + add [current_seg], ax + + mov eax, [current_lba] + movzx ebx, word [chunk_sectors] + add eax, ebx + mov [current_lba], eax + + mov ax, [remaining_sectors] + sub ax, [chunk_sectors] + mov [remaining_sectors], ax + jmp .next_chunk + +.error: + stc + ret + +.done: + clc + ret + +print_string: + lodsb + test al, al + jz .done + mov ah, 0x0E + mov bh, 0x00 + mov bl, 0x0F + int 0x10 + jmp print_string +.done: + ret + +boot_drive: db 0 +msg_stage2: db "Aster Bootloader stage2", 13, 10, 0 +msg_kernel_loaded: db "Kernel read OK", 13, 10, 0 +msg_load_error: db "Kernel load error", 13, 10, 0 + +align 8 +dap: + db 0x10 + db 0x00 + dw 0 + dw 0 + dw 0 + dd 0 + +remaining_sectors: dw 0 +chunk_sectors: dw 0 +current_seg: dw 0 +current_lba: dd 0 + dd 0 + +align 8 +gdt_start: + dq 0x0000000000000000 + dq 0x00CF9A000000FFFF + dq 0x00CF92000000FFFF + dq 0x00AF9A000000FFFF + dq 0x00CF92000000FFFF +gdt_end: + +gdt_descriptor: + dw gdt_end - gdt_start - 1 + dd gdt_start + +[bits 32] +protected_entry: + mov word [0xB8000], 0x0F50 + + mov ax, 0x10 + mov ds, ax + mov es, ax + mov fs, ax + mov gs, ax + mov ss, ax + mov esp, 0x9FC00 + + mov esi, 0x00010000 + mov edi, 0x00100000 + mov ecx, (KERNEL_SECTORS * 512) / 4 + rep movsd + + call setup_paging + + mov word [0xB8002], 0x0F47 + + mov eax, cr4 + or eax, (1 << 5) + mov cr4, eax + + mov ecx, 0xC0000080 + rdmsr + or eax, (1 << 8) + wrmsr + + mov eax, PML4_ADDR + mov cr3, eax + + mov eax, cr0 + or eax, 0x80000001 + mov cr0, eax + + mov word [0xB8004], 0x0F4C + + jmp 0x18:long_mode_entry + +setup_paging: + mov edi, PML4_ADDR + mov ecx, (4096 * 3) / 4 + xor eax, eax + rep stosd + + mov dword [PML4_ADDR], PDPT_ADDR | 0x03 + mov dword [PML4_ADDR + 4], 0 + + mov dword [PDPT_ADDR], PD_ADDR | 0x03 + mov dword [PDPT_ADDR + 4], 0 + + mov edi, PD_ADDR + xor ebx, ebx + mov ecx, 512 + +.map_pd: + mov eax, ebx + or eax, 0x83 + mov dword [edi], eax + mov dword [edi + 4], 0 + add ebx, 0x200000 + add edi, 8 + loop .map_pd + + ret + +[bits 64] +long_mode_entry: + mov word [0xB8006], 0x0F4D + + mov ax, 0x20 + mov ds, ax + mov es, ax + mov fs, ax + mov gs, ax + mov ss, ax + mov rsp, 0x200000 + + mov word [0xB8008], 0x0F4A + + mov rax, 0x00100000 + jmp rax + +halt64: + hlt + jmp halt64 diff --git a/drivers/display.c b/drivers/display.c new file mode 100644 index 0000000..856a568 --- /dev/null +++ b/drivers/display.c @@ -0,0 +1,190 @@ +/* + * AsterOS Kernel + * Autor: Pavel Kalaš + * Rok: 2026 + * + */ + +/* + * Tento ovladac realizuje textovou konzoli nad VGA pameti na adrese 0xB8000. + * Ridi pozici kurzoru, barvy znaku, zpracovani ridicich znaku + * a automaticky scroll, kdyz vystup presahne posledni radek obrazovky. + */ + +#include "display.h" + +#define VGA_WIDTH 80 +#define VGA_HEIGHT 25 +#define VGA_CONTENT_HEIGHT (VGA_HEIGHT - 1) + +static volatile u16 *const vga = (u16 *)0xB8000; +static u8 color = 0x0F; +static usize row = 0; +static usize col = 0; + +static inline void outb(u16 port, u8 value) { + __asm__ volatile ("outb %0, %1" : : "a"(value), "Nd"(port)); +} + +static void update_hw_cursor(void) { + u16 pos = (u16)(row * VGA_WIDTH + col); + + outb(0x3D4, 0x0F); + outb(0x3D5, (u8)(pos & 0xFF)); + outb(0x3D4, 0x0E); + outb(0x3D5, (u8)((pos >> 8) & 0xFF)); +} + +static inline u16 vga_entry(char c, u8 clr) { + return (u16)c | ((u16)clr << 8); +} + +static void scroll_if_needed(void) { + usize r; + usize c; + + if (row < VGA_CONTENT_HEIGHT) { + return; + } + + for (r = 1; r < VGA_CONTENT_HEIGHT; ++r) { + for (c = 0; c < VGA_WIDTH; ++c) { + vga[(r - 1) * VGA_WIDTH + c] = vga[r * VGA_WIDTH + c]; + } + } + + for (c = 0; c < VGA_WIDTH; ++c) { + vga[(VGA_CONTENT_HEIGHT - 1) * VGA_WIDTH + c] = vga_entry(' ', color); + } + + row = VGA_CONTENT_HEIGHT - 1; +} + +void display_set_color(u8 fg, u8 bg) { + color = (u8)((bg << 4) | (fg & 0x0F)); +} + +void display_clear(void) { + usize i; + + for (i = 0; i < VGA_WIDTH * VGA_HEIGHT; ++i) { + vga[i] = vga_entry(' ', color); + } + + row = 0; + col = 0; + update_hw_cursor(); +} + +void display_init(void) { + display_set_color(0x0F, 0x00); + display_clear(); +} + +void display_putc(char c) { + if (c == '\n') { + col = 0; + ++row; + scroll_if_needed(); + update_hw_cursor(); + return; + } + + if (c == '\r') { + col = 0; + update_hw_cursor(); + return; + } + + if (c == '\b') { + if (col > 0) { + --col; + } else if (row > 0) { + --row; + col = VGA_WIDTH - 1; + } + + vga[row * VGA_WIDTH + col] = vga_entry(' ', color); + update_hw_cursor(); + return; + } + + if (c == '\t') { + col = (col + 4) & ~3ULL; + if (col >= VGA_WIDTH) { + col = 0; + ++row; + scroll_if_needed(); + } + update_hw_cursor(); + return; + } + + vga[row * VGA_WIDTH + col] = vga_entry(c, color); + ++col; + + if (col >= VGA_WIDTH) { + col = 0; + ++row; + scroll_if_needed(); + } + + update_hw_cursor(); +} + +void display_write(const char *text) { + while (*text) { + display_putc(*text++); + } +} + +void display_get_cursor(usize *out_row, usize *out_col) { + if (out_row) { + *out_row = row; + } + if (out_col) { + *out_col = col; + } +} + +void display_set_cursor(usize new_row, usize new_col) { + if (new_row >= VGA_HEIGHT) { + new_row = VGA_HEIGHT - 1; + } + if (new_col >= VGA_WIDTH) { + new_col = VGA_WIDTH - 1; + } + + row = new_row; + col = new_col; + update_hw_cursor(); +} + +void display_fill_row(usize target_row, char ch, u8 fg, u8 bg) { + usize c; + u8 clr; + + if (target_row >= VGA_HEIGHT) { + return; + } + + clr = (u8)((bg << 4) | (fg & 0x0F)); + for (c = 0; c < VGA_WIDTH; ++c) { + vga[target_row * VGA_WIDTH + c] = vga_entry(ch, clr); + } +} + +void display_write_at(usize target_row, usize target_col, const char *text, u8 fg, u8 bg) { + usize c = target_col; + u8 clr; + + if (!text || target_row >= VGA_HEIGHT || target_col >= VGA_WIDTH) { + return; + } + + clr = (u8)((bg << 4) | (fg & 0x0F)); + while (*text && c < VGA_WIDTH) { + vga[target_row * VGA_WIDTH + c] = vga_entry(*text++, clr); + ++c; + } +} diff --git a/drivers/keyboard.c b/drivers/keyboard.c new file mode 100644 index 0000000..cbf6287 --- /dev/null +++ b/drivers/keyboard.c @@ -0,0 +1,275 @@ +/* + * AsterOS Kernel + * Autor: Pavel Kalaš + * Rok: 2026 + * + */ + +/* + * Tento soubor implementuje zakladni klavesnicovy vstup pres PS/2 porty. + * Preklada scancode na ASCII znaky a poskytuje funkci pro nacitani radku, + * kterou pouziva shell pro prijem prikazu od uzivatele. + */ + +#include "display.h" +#include "drivers.h" + +#define KBD_HISTORY_MAX 32 +#define KBD_HISTORY_LINE_MAX 128 + +static char g_history[KBD_HISTORY_MAX][KBD_HISTORY_LINE_MAX]; +static int g_history_count = 0; +static int g_history_head = 0; +static int g_ctrl_down = 0; + +static inline unsigned char inb(unsigned short port) { + unsigned char ret; + __asm__ volatile ("inb %1, %0" : "=a"(ret) : "Nd"(port)); + return ret; +} + +static const char keymap[128] = { + 0, 27, '1', '2', '3', '4', '5', '6', '7', '8', '9', '0', '-', '=', '\b', '\t', + 'q', 'w', 'e', 'r', 't', 'y', 'u', 'i', 'o', 'p', '[', ']', '\n', 0, 'a', 's', + 'd', 'f', 'g', 'h', 'j', 'k', 'l', ';', '\'', '`', 0, '\\', 'z', 'x', 'c', 'v', + 'b', 'n', 'm', ',', '.', '/', 0, '*', 0, ' ', +}; + +void keyboard_init(void) { +} + +int keyboard_try_read_key(void) { + unsigned char sc; + int out = -1; + + if ((inb(0x64) & 1) == 0) { + return -1; + } + + sc = inb(0x60); + + if (sc == 0xE0) { + unsigned char ext; + + if ((inb(0x64) & 1) == 0) { + return -1; + } + + ext = inb(0x60); + if (ext == 0x1D) { + g_ctrl_down = 1; + return -1; + } + + if (ext == 0x9D) { + g_ctrl_down = 0; + return -1; + } + + if (ext & 0x80) { + return -1; + } + + if (ext == 0x48) { + out = ASTER_KEY_UP; + return out; + } + + if (ext == 0x50) { + out = ASTER_KEY_DOWN; + return out; + } + + if (ext == 0x4B) { + out = ASTER_KEY_LEFT; + return out; + } + + if (ext == 0x4D) { + out = ASTER_KEY_RIGHT; + return out; + } + + return -1; + } + + if (sc == 0x1D) { + g_ctrl_down = 1; + return -1; + } + + if (sc == 0x9D) { + g_ctrl_down = 0; + return -1; + } + + if (sc & 0x80) { + return -1; + } + + if (sc == 0x3B) { + out = ASTER_KEY_F1; + return out; + } + + if (sc == 0x3C) { + out = ASTER_KEY_F2; + return out; + } + + if (sc == 0x1F && g_ctrl_down) { + out = 19; /* Ctrl+S */ + return out; + } + + if (sc < 128 && keymap[sc]) { + out = keymap[sc]; + return out; + } + + return -1; +} + +int keyboard_read_key(void) { + for (;;) { + int k = keyboard_try_read_key(); + if (k != -1) { + return k; + } + + __asm__ volatile ("pause"); + } +} + +static void clear_current_line(int len) { + int j; + + for (j = 0; j < len; ++j) { + display_putc('\b'); + } +} + +static void set_line_from_history(char *buffer, int *len, int max_len, const char *src) { + int j = 0; + int limit = max_len - 1; + + clear_current_line(*len); + while (src[j] && j < limit) { + buffer[j] = src[j]; + display_putc(src[j]); + ++j; + } + + buffer[j] = '\0'; + *len = j; +} + +static void history_push(const char *line) { + int i; + int slot; + + if (!line || !line[0]) { + return; + } + + if (g_history_count > 0) { + int last = (g_history_head + KBD_HISTORY_MAX - 1) % KBD_HISTORY_MAX; + if (g_history[last][0] != '\0') { + i = 0; + while (line[i] && g_history[last][i] && line[i] == g_history[last][i]) { + ++i; + } + if (line[i] == '\0' && g_history[last][i] == '\0') { + return; + } + } + } + + slot = g_history_head; + for (i = 0; i < KBD_HISTORY_LINE_MAX - 1 && line[i]; ++i) { + g_history[slot][i] = line[i]; + } + g_history[slot][i] = '\0'; + + g_history_head = (g_history_head + 1) % KBD_HISTORY_MAX; + if (g_history_count < KBD_HISTORY_MAX) { + ++g_history_count; + } +} + +int keyboard_readline(char *buffer, int max_len) { + int i = 0; + int history_nav = -1; + + if (max_len <= 1) { + return 0; + } + + for (;;) { + int c = keyboard_read_key(); + + if (c == ASTER_KEY_F1 || c == ASTER_KEY_F2) { + continue; + } + + if (c == ASTER_KEY_UP) { + if (g_history_count == 0) { + continue; + } + + if (history_nav < g_history_count - 1) { + ++history_nav; + } + + { + int idx = (g_history_head + KBD_HISTORY_MAX - 1 - history_nav) % KBD_HISTORY_MAX; + set_line_from_history(buffer, &i, max_len, g_history[idx]); + } + + continue; + } + + if (c == ASTER_KEY_DOWN) { + if (g_history_count == 0) { + continue; + } + + if (history_nav > 0) { + --history_nav; + { + int idx = (g_history_head + KBD_HISTORY_MAX - 1 - history_nav) % KBD_HISTORY_MAX; + set_line_from_history(buffer, &i, max_len, g_history[idx]); + } + } else if (history_nav == 0) { + history_nav = -1; + clear_current_line(i); + i = 0; + buffer[0] = '\0'; + } + + continue; + } + + if (c == '\n') { + display_putc('\n'); + buffer[i] = '\0'; + history_push(buffer); + return i; + } + + if (c == '\b') { + if (i > 0) { + --i; + display_putc('\b'); + } + continue; + } + + if (i < max_len - 1) { + buffer[i++] = (char)c; + display_putc((char)c); + buffer[i] = '\0'; + history_nav = -1; + } + } +} diff --git a/drivers/storage.c b/drivers/storage.c new file mode 100644 index 0000000..f60e900 --- /dev/null +++ b/drivers/storage.c @@ -0,0 +1,669 @@ +/* + * AsterOS Kernel + * Autor: Pavel Kalas + * Rok: 2026 + * + */ + +/* + * Tento modul implementuje AsterFS nad realnym ATA diskem v QEMU. + * Data se nahravaji pri startu a po kazde zmene se synchronizuji + * do sektoru na druhem IDE disku, aby pretrvala mezi restarty. + */ + +#include "storage.h" +#include "string.h" + +#define ATA_IO_BASE 0x1F0 +#define ATA_CTRL_BASE 0x3F6 +#define ATA_DRIVE_SLAVE 0xF0 + +#define ATA_REG_DATA 0 +#define ATA_REG_SECCOUNT0 2 +#define ATA_REG_LBA0 3 +#define ATA_REG_LBA1 4 +#define ATA_REG_LBA2 5 +#define ATA_REG_HDDEVSEL 6 +#define ATA_REG_COMMAND 7 +#define ATA_REG_STATUS 7 + +#define ATA_CMD_READ_PIO 0x20 +#define ATA_CMD_WRITE_PIO 0x30 +#define ATA_CMD_CACHE_FLUSH 0xE7 + +#define ATA_SR_BSY 0x80 +#define ATA_SR_DRQ 0x08 +#define ATA_SR_ERR 0x01 + +#define ASTERFS_MAGIC "ASTERFS1" +#define ASTERFS_VERSION 1U + +#define ASTERFS_NODE_BYTES (ASTERFS_NAME_LEN + 1 + 2 + 1 + ASTERFS_DATA_LEN) +#define ASTERFS_NODE_SECTORS (((ASTERFS_MAX_FILES * ASTERFS_NODE_BYTES) + 511) / 512) +#define ASTERFS_DISK_START_LBA 1U + +typedef struct { + char magic[8]; + u32 version; + u32 nodes_used; + u32 reserved; + u8 pad[512 - 8 - 4 - 4 - 4]; +} asterfs_superblock_t; + +typedef struct { + char name[ASTERFS_NAME_LEN]; + u8 is_dir; + u16 size; + u8 reserved; + u8 data[ASTERFS_DATA_LEN]; +} asterfs_disk_node_t; + +static asterfs_node_t nodes[ASTERFS_MAX_FILES]; +static int nodes_used = 0; +static int disk_ready = 0; +static u8 g_sector_buffer[512]; +static u8 g_nodes_blob[ASTERFS_MAX_FILES * ASTERFS_NODE_BYTES]; + +static inline void outb(unsigned short port, unsigned char value) { + __asm__ volatile ("outb %0, %1" : : "a"(value), "Nd"(port)); +} + +static inline unsigned char inb(unsigned short port) { + unsigned char ret; + __asm__ volatile ("inb %1, %0" : "=a"(ret) : "Nd"(port)); + return ret; +} + +static inline void outw(unsigned short port, unsigned short value) { + __asm__ volatile ("outw %0, %1" : : "a"(value), "Nd"(port)); +} + +static inline unsigned short inw(unsigned short port) { + unsigned short ret; + __asm__ volatile ("inw %1, %0" : "=a"(ret) : "Nd"(port)); + return ret; +} + +static void ata_delay_400ns(void) { + (void)inb(ATA_CTRL_BASE); + (void)inb(ATA_CTRL_BASE); + (void)inb(ATA_CTRL_BASE); + (void)inb(ATA_CTRL_BASE); +} + +static int ata_wait_not_busy(void) { + unsigned int timeout = 200000U; + while (timeout-- > 0) { + unsigned char s = inb(ATA_IO_BASE + ATA_REG_STATUS); + if (s == 0x00 || s == 0xFF) { + return -1; + } + if ((s & ATA_SR_BSY) == 0) { + return 0; + } + } + return -1; +} + +static int ata_wait_drq(void) { + unsigned int timeout = 200000U; + while (timeout-- > 0) { + unsigned char s = inb(ATA_IO_BASE + ATA_REG_STATUS); + if (s == 0x00 || s == 0xFF) { + return -1; + } + if (s & ATA_SR_ERR) { + return -1; + } + if ((s & ATA_SR_BSY) == 0 && (s & ATA_SR_DRQ) != 0) { + return 0; + } + } + return -1; +} + +static int ata_select_drive_lba(u32 lba) { + if (ata_wait_not_busy() != 0) { + return -1; + } + + outb(ATA_IO_BASE + ATA_REG_HDDEVSEL, (unsigned char)(ATA_DRIVE_SLAVE | ((lba >> 24) & 0x0F))); + ata_delay_400ns(); + return 0; +} + +static int ata_probe_slave(void) { + unsigned char s; + + outb(ATA_IO_BASE + ATA_REG_HDDEVSEL, ATA_DRIVE_SLAVE); + ata_delay_400ns(); + + s = inb(ATA_IO_BASE + ATA_REG_STATUS); + if (s == 0x00 || s == 0xFF) { + return -1; + } + + return ata_wait_not_busy(); +} + +static int ata_read_sector(u32 lba, u8 *buf512) { + int i; + + if (!buf512) { + return -1; + } + + if (ata_select_drive_lba(lba) != 0) { + return -1; + } + + outb(ATA_IO_BASE + ATA_REG_SECCOUNT0, 1); + outb(ATA_IO_BASE + ATA_REG_LBA0, (unsigned char)(lba & 0xFF)); + outb(ATA_IO_BASE + ATA_REG_LBA1, (unsigned char)((lba >> 8) & 0xFF)); + outb(ATA_IO_BASE + ATA_REG_LBA2, (unsigned char)((lba >> 16) & 0xFF)); + outb(ATA_IO_BASE + ATA_REG_COMMAND, ATA_CMD_READ_PIO); + + if (ata_wait_drq() != 0) { + return -1; + } + + for (i = 0; i < 256; ++i) { + unsigned short w = inw(ATA_IO_BASE + ATA_REG_DATA); + buf512[i * 2] = (u8)(w & 0xFF); + buf512[i * 2 + 1] = (u8)((w >> 8) & 0xFF); + } + + return 0; +} + +static int ata_write_sector(u32 lba, const u8 *buf512) { + int i; + + if (!buf512) { + return -1; + } + + if (ata_select_drive_lba(lba) != 0) { + return -1; + } + + outb(ATA_IO_BASE + ATA_REG_SECCOUNT0, 1); + outb(ATA_IO_BASE + ATA_REG_LBA0, (unsigned char)(lba & 0xFF)); + outb(ATA_IO_BASE + ATA_REG_LBA1, (unsigned char)((lba >> 8) & 0xFF)); + outb(ATA_IO_BASE + ATA_REG_LBA2, (unsigned char)((lba >> 16) & 0xFF)); + outb(ATA_IO_BASE + ATA_REG_COMMAND, ATA_CMD_WRITE_PIO); + + if (ata_wait_drq() != 0) { + return -1; + } + + for (i = 0; i < 256; ++i) { + unsigned short w = (unsigned short)buf512[i * 2] | ((unsigned short)buf512[i * 2 + 1] << 8); + outw(ATA_IO_BASE + ATA_REG_DATA, w); + } + + outb(ATA_IO_BASE + ATA_REG_COMMAND, ATA_CMD_CACHE_FLUSH); + if (ata_wait_not_busy() != 0) { + return -1; + } + + return 0; +} + +static void fs_reset_memory(void) { + int i; + + for (i = 0; i < ASTERFS_MAX_FILES; ++i) { + nodes[i].name[0] = '\0'; + nodes[i].is_dir = 0; + nodes[i].size = 0; + } + + nodes_used = 0; +} + +static void fs_encode_nodes(u8 *out) { + int i; + + for (i = 0; i < ASTERFS_MAX_FILES; ++i) { + asterfs_disk_node_t d; + usize off = (usize)i * ASTERFS_NODE_BYTES; + + aster_memset(&d, 0, sizeof(d)); + aster_memcpy(d.name, nodes[i].name, ASTERFS_NAME_LEN); + d.is_dir = nodes[i].is_dir; + d.size = nodes[i].size; + aster_memcpy(d.data, nodes[i].data, ASTERFS_DATA_LEN); + + aster_memcpy(out + off, &d, ASTERFS_NODE_BYTES); + } +} + +static void fs_decode_nodes(const u8 *in) { + int i; + + for (i = 0; i < ASTERFS_MAX_FILES; ++i) { + asterfs_disk_node_t d; + usize off = (usize)i * ASTERFS_NODE_BYTES; + + aster_memcpy(&d, in + off, ASTERFS_NODE_BYTES); + + aster_memset(nodes[i].name, 0, ASTERFS_NAME_LEN); + aster_memcpy(nodes[i].name, d.name, ASTERFS_NAME_LEN); + nodes[i].is_dir = d.is_dir; + nodes[i].size = d.size; + if (nodes[i].size > ASTERFS_DATA_LEN) { + nodes[i].size = ASTERFS_DATA_LEN; + } + aster_memcpy(nodes[i].data, d.data, ASTERFS_DATA_LEN); + } +} + +static int fs_flush_disk(void) { + asterfs_superblock_t super; + unsigned int sector; + + if (!disk_ready) { + return 0; + } + + aster_memset(&super, 0, sizeof(super)); + aster_memcpy(super.magic, ASTERFS_MAGIC, 8); + super.version = ASTERFS_VERSION; + super.nodes_used = (u32)nodes_used; + + aster_memset(g_sector_buffer, 0, sizeof(g_sector_buffer)); + aster_memcpy(g_sector_buffer, &super, sizeof(super)); + if (ata_write_sector(ASTERFS_DISK_START_LBA, g_sector_buffer) != 0) { + return -1; + } + + fs_encode_nodes(g_nodes_blob); + for (sector = 0; sector < ASTERFS_NODE_SECTORS; ++sector) { + usize off = (usize)sector * 512; + aster_memset(g_sector_buffer, 0, sizeof(g_sector_buffer)); + if (off < sizeof(g_nodes_blob)) { + usize left = sizeof(g_nodes_blob) - off; + usize chunk = left < 512 ? left : 512; + aster_memcpy(g_sector_buffer, g_nodes_blob + off, chunk); + } + + if (ata_write_sector(ASTERFS_DISK_START_LBA + 1U + sector, g_sector_buffer) != 0) { + return -1; + } + } + + return 0; +} + +static int fs_load_disk(void) { + asterfs_superblock_t super; + unsigned int sector; + + if (!disk_ready) { + return -1; + } + + if (ata_read_sector(ASTERFS_DISK_START_LBA, g_sector_buffer) != 0) { + return -1; + } + + aster_memcpy(&super, g_sector_buffer, sizeof(super)); + + if (aster_strncmp(super.magic, ASTERFS_MAGIC, 8) != 0 || super.version != ASTERFS_VERSION) { + fs_reset_memory(); + return fs_flush_disk(); + } + + for (sector = 0; sector < ASTERFS_NODE_SECTORS; ++sector) { + usize off = (usize)sector * 512; + if (ata_read_sector(ASTERFS_DISK_START_LBA + 1U + sector, g_sector_buffer) != 0) { + return -1; + } + + if (off < sizeof(g_nodes_blob)) { + usize left = sizeof(g_nodes_blob) - off; + usize chunk = left < 512 ? left : 512; + aster_memcpy(g_nodes_blob + off, g_sector_buffer, chunk); + } + } + + fs_decode_nodes(g_nodes_blob); + if (super.nodes_used > ASTERFS_MAX_FILES) { + nodes_used = ASTERFS_MAX_FILES; + } else { + nodes_used = (int)super.nodes_used; + } + + return 0; +} + +static int is_root(const char *path) { + return path && path[0] == '/' && path[1] == '\0'; +} + +static int path_parent_exists(const char *path) { + int i; + int last = -1; + char parent[ASTERFS_NAME_LEN]; + + if (!path || path[0] != '/') { + return 0; + } + + for (i = 0; path[i] != '\0'; ++i) { + if (path[i] == '/') { + last = i; + } + } + + if (last <= 0) { + return 1; + } + + if ((usize)last >= ASTERFS_NAME_LEN) { + return 0; + } + + aster_memcpy(parent, path, (usize)last); + parent[last] = '\0'; + + for (i = 0; i < nodes_used; ++i) { + if (nodes[i].is_dir && aster_strcmp(nodes[i].name, parent) == 0) { + return 1; + } + } + + return 0; +} + +static int path_is_child(const char *parent, const char *path, const char **leaf_start) { + usize parent_len; + const char *rest; + const char *p; + + if (!parent || !path || path[0] != '/') { + return 0; + } + + if (is_root(parent)) { + if (path[0] != '/' || path[1] == '\0') { + return 0; + } + + rest = path + 1; + p = rest; + while (*p && *p != '/') { + ++p; + } + + if (*p != '\0') { + return 0; + } + + *leaf_start = rest; + return 1; + } + + parent_len = aster_strlen(parent); + if (aster_strncmp(path, parent, parent_len) != 0) { + return 0; + } + + if (path[parent_len] != '/') { + return 0; + } + + rest = path + parent_len + 1; + if (*rest == '\0') { + return 0; + } + + p = rest; + while (*p && *p != '/') { + ++p; + } + + if (*p != '\0') { + return 0; + } + + *leaf_start = rest; + return 1; +} + +void storage_init(void) { + asterfs_init(); +} + +void asterfs_init(void) { + fs_reset_memory(); + + disk_ready = (ata_probe_slave() == 0); + if (!disk_ready) { + return; + } + + if (fs_load_disk() != 0) { + fs_reset_memory(); + (void)fs_flush_disk(); + } +} + +static int find_node(const char *name) { + int i; + + for (i = 0; i < nodes_used; ++i) { + if (aster_strcmp(nodes[i].name, name) == 0) { + return i; + } + } + + return -1; +} + +int asterfs_create_file(const char *name) { + usize len; + + if (!name || name[0] != '/' || nodes_used >= ASTERFS_MAX_FILES) { + return -1; + } + + if (!path_parent_exists(name)) { + return -1; + } + + if (find_node(name) >= 0) { + return 0; + } + + len = aster_strlen(name); + if (len >= ASTERFS_NAME_LEN) { + return -1; + } + + aster_memset(nodes[nodes_used].name, 0, ASTERFS_NAME_LEN); + aster_memcpy(nodes[nodes_used].name, name, len); + nodes[nodes_used].size = 0; + nodes[nodes_used].is_dir = 0; + ++nodes_used; + (void)fs_flush_disk(); + return 0; +} + +int asterfs_create_dir(const char *name) { + usize len; + + if (!name || name[0] != '/' || is_root(name) || nodes_used >= ASTERFS_MAX_FILES) { + return -1; + } + + if (!path_parent_exists(name)) { + return -1; + } + + if (find_node(name) >= 0) { + return 0; + } + + len = aster_strlen(name); + if (len >= ASTERFS_NAME_LEN) { + return -1; + } + + aster_memset(nodes[nodes_used].name, 0, ASTERFS_NAME_LEN); + aster_memcpy(nodes[nodes_used].name, name, len); + nodes[nodes_used].size = 0; + nodes[nodes_used].is_dir = 1; + ++nodes_used; + (void)fs_flush_disk(); + return 0; +} + +int asterfs_remove_file(const char *name) { + int idx; + int i; + + idx = find_node(name); + if (idx < 0 || nodes[idx].is_dir) { + return -1; + } + + for (i = idx; i < nodes_used - 1; ++i) { + nodes[i] = nodes[i + 1]; + } + + --nodes_used; + (void)fs_flush_disk(); + return 0; +} + +int asterfs_remove_dir(const char *name) { + int idx; + int i; + const char *leaf; + + if (is_root(name)) { + return -1; + } + + idx = find_node(name); + if (idx < 0 || !nodes[idx].is_dir) { + return -1; + } + + for (i = 0; i < nodes_used; ++i) { + if (i == idx) { + continue; + } + + if (path_is_child(name, nodes[i].name, &leaf)) { + return -1; + } + } + + for (i = idx; i < nodes_used - 1; ++i) { + nodes[i] = nodes[i + 1]; + } + + --nodes_used; + (void)fs_flush_disk(); + return 0; +} + +int asterfs_write_file(const char *name, const u8 *data, u16 len) { + int idx; + + if (!name || !data || name[0] != '/') { + return -1; + } + + idx = find_node(name); + if (idx < 0) { + if (asterfs_create_file(name) != 0) { + return -1; + } + idx = find_node(name); + } + + if (idx < 0 || nodes[idx].is_dir) { + return -1; + } + + if (len > ASTERFS_DATA_LEN) { + len = ASTERFS_DATA_LEN; + } + + aster_memset(nodes[idx].data, 0, ASTERFS_DATA_LEN); + aster_memcpy(nodes[idx].data, data, len); + nodes[idx].size = len; + (void)fs_flush_disk(); + return len; +} + +int asterfs_read_file(const char *name, u8 *out, u16 max_len) { + int idx; + u16 len; + + if (!name || !out || name[0] != '/') { + return -1; + } + + idx = find_node(name); + if (idx < 0 || nodes[idx].is_dir) { + return -1; + } + + len = nodes[idx].size; + if (len > max_len) { + len = max_len; + } + + aster_memcpy(out, nodes[idx].data, len); + return len; +} + +void asterfs_list(void (*cb)(const char *name, u8 is_dir, u16 size)) { + int i; + + for (i = 0; i < nodes_used; ++i) { + cb(nodes[i].name, nodes[i].is_dir, nodes[i].size); + } +} + +int asterfs_get_type(const char *name) { + int idx; + + if (is_root(name)) { + return 1; + } + + idx = find_node(name); + if (idx < 0) { + return -1; + } + + return nodes[idx].is_dir ? 1 : 0; +} + +void asterfs_list_dir(const char *path, void (*cb)(const char *name, u8 is_dir, u16 size)) { + int i; + const char *leaf = 0; + char temp[ASTERFS_NAME_LEN]; + usize n; + + for (i = 0; i < nodes_used; ++i) { + if (!path_is_child(path, nodes[i].name, &leaf)) { + continue; + } + + n = aster_strlen(leaf); + if (n >= ASTERFS_NAME_LEN) { + n = ASTERFS_NAME_LEN - 1; + } + + aster_memcpy(temp, leaf, n); + temp[n] = '\0'; + cb(temp, nodes[i].is_dir, nodes[i].size); + } +} diff --git a/drivers/timer.c b/drivers/timer.c new file mode 100644 index 0000000..0048640 --- /dev/null +++ b/drivers/timer.c @@ -0,0 +1,83 @@ +/* + * AsterOS Kernel + * Autor: Pavel Kalaš + * Rok: 2026 + * + */ + +/* + * Tento soubor implementuje ovladac PIT casovace. + * Nastavuje frekvenci hardwaroveho casovace, drzi globalni citac tiku + * a poskytuje blokujici uspani jadra na zadany pocet milisekund. + */ + +#include "timer.h" + +static volatile unsigned long g_ticks = 0; + +static inline unsigned long long read_tsc(void) { + unsigned int lo; + unsigned int hi; + __asm__ volatile ("rdtsc" : "=a"(lo), "=d"(hi)); + return ((unsigned long long)hi << 32) | (unsigned long long)lo; +} + +static int interrupts_enabled(void) { + unsigned long long rflags; + __asm__ volatile ("pushfq; popq %0" : "=r"(rflags)); + return (rflags & (1ULL << 9)) != 0; +} + +static void busy_delay_ms(unsigned long ms) { + unsigned long long start = read_tsc(); + unsigned long long wait_cycles = (unsigned long long)ms * 1000000ULL; + unsigned long long end = start + wait_cycles; + + while (read_tsc() < end) { + __asm__ volatile ("pause"); + } +} + +static inline void outb(unsigned short port, unsigned char value) { + __asm__ volatile ("outb %0, %1" : : "a"(value), "Nd"(port)); +} + +void timer_init(unsigned int hz) { + unsigned int divisor; + + if (hz == 0) { + hz = 100; + } + + divisor = 1193180U / hz; + + outb(0x43, 0x36); + outb(0x40, (unsigned char)(divisor & 0xFF)); + outb(0x40, (unsigned char)((divisor >> 8) & 0xFF)); +} + +unsigned long timer_ticks(void) { + return g_ticks; +} + +void timer_tick_advance(void) { + ++g_ticks; +} + +void timer_sleep_ms(unsigned long ms) { + if (!interrupts_enabled()) { + busy_delay_ms(ms); + return; + } + + unsigned long start = timer_ticks(); + unsigned long wait_ticks = (ms + 9UL) / 10UL; + + if (wait_ticks == 0) { + wait_ticks = 1; + } + + while ((timer_ticks() - start) < wait_ticks) { + __asm__ volatile ("hlt"); + } +} diff --git a/include/aster_api.h b/include/aster_api.h new file mode 100644 index 0000000..71e227e --- /dev/null +++ b/include/aster_api.h @@ -0,0 +1,35 @@ +/* + * AsterOS Kernel + * Autor: Pavel Kalas + * Rok: 2026 + * + */ + +/* + * Tento header definuje experimentalni API vrstvu pro C aplikace. + * API je navrzene pro budouci user-space runtime, ale lze ho uz ted + * pouzit pro jednotne volani kernel sluzeb v internich aplikacich. + */ + +#ifndef ASTER_API_H +#define ASTER_API_H + +#include "types.h" + +#define ASTER_APP_API_VERSION 1 + +typedef struct { + u32 api_version; + u32 reserved; +} aster_app_context_t; + +int aster_api_print(const char *text); +int aster_api_clear(void); +int aster_api_ticks(u64 *out_ticks); +void *aster_api_alloc(usize size); +int aster_api_file_create(const char *path); +int aster_api_file_read(const char *path, u8 *out, u16 max_len); +int aster_api_file_write(const char *path, const u8 *data, u16 len); +int aster_api_process_spawn(void (*entry)(void), const char *name, u8 priority); + +#endif diff --git a/include/cpu.h b/include/cpu.h new file mode 100644 index 0000000..9769b70 --- /dev/null +++ b/include/cpu.h @@ -0,0 +1,49 @@ +/* + * AsterOS Kernel + * Autor: Pavel Kalaš + * Rok: 2026 + * + */ + +/* + * Tento header definuje datovy tvar CPU kontextu ulozeneho pri preruseni. + * Obsahuje strukturu interrupt_frame_t a rozhrani pro inicializaci CPU, + * nastaveni tabulek preruseni a centralni dispatch obsluhy preruseni. + */ + +#ifndef ASTER_CPU_H +#define ASTER_CPU_H + +#include "types.h" + +typedef struct { + u64 r15; + u64 r14; + u64 r13; + u64 r12; + u64 r11; + u64 r10; + u64 r9; + u64 r8; + u64 rsi; + u64 rdi; + u64 rbp; + u64 rdx; + u64 rcx; + u64 rbx; + u64 rax; + u64 vector; + u64 error; + u64 rip; + u64 cs; + u64 rflags; + u64 rsp; + u64 ss; +} interrupt_frame_t; + +void cpu_init(void); +void interrupts_init(void); +void interrupt_dispatch(interrupt_frame_t *frame); +void cpu_halt(void); + +#endif diff --git a/include/display.h b/include/display.h new file mode 100644 index 0000000..6e24250 --- /dev/null +++ b/include/display.h @@ -0,0 +1,29 @@ +/* + * AsterOS Kernel + * Autor: Pavel Kalaš + * Rok: 2026 + * + */ + +/* + * Tento header vystavuje jednotne API pro textovy vystup v VGA rezimu. + * Definuje operace pro inicializaci, cisteni obrazovky, zapis znaku, + * zapis retezce a zmenu barevne palety textove konzole. + */ + +#ifndef ASTER_DISPLAY_H +#define ASTER_DISPLAY_H + +#include "types.h" + +void display_init(void); +void display_clear(void); +void display_putc(char c); +void display_write(const char *text); +void display_set_color(u8 fg, u8 bg); +void display_get_cursor(usize *out_row, usize *out_col); +void display_set_cursor(usize new_row, usize new_col); +void display_fill_row(usize target_row, char ch, u8 fg, u8 bg); +void display_write_at(usize target_row, usize target_col, const char *text, u8 fg, u8 bg); + +#endif diff --git a/include/drivers.h b/include/drivers.h new file mode 100644 index 0000000..b1f1c2f --- /dev/null +++ b/include/drivers.h @@ -0,0 +1,34 @@ +/* + * AsterOS Kernel + * Autor: Pavel Kalaš + * Rok: 2026 + * + */ + +/* + * Tento soubor slouzi jako centralni rozcestnik pro verejne ovladace jadra. + * Drzi pohromade deklarace klavesnice, casovace a storage vrstvy, + * aby je kernel mohl jednoduse inicializovat z jednoho mista. + */ + +#ifndef ASTER_DRIVERS_H +#define ASTER_DRIVERS_H + +#define ASTER_KEY_F1 0x101 +#define ASTER_KEY_F2 0x102 +#define ASTER_KEY_UP 0x103 +#define ASTER_KEY_DOWN 0x104 +#define ASTER_KEY_LEFT 0x105 +#define ASTER_KEY_RIGHT 0x106 + +void keyboard_init(void); +int keyboard_read_key(void); +int keyboard_try_read_key(void); +int keyboard_readline(char *buffer, int max_len); + +void timer_init(unsigned int hz); +unsigned long timer_ticks(void); + +void storage_init(void); + +#endif diff --git a/include/memory.h b/include/memory.h new file mode 100644 index 0000000..c5860f0 --- /dev/null +++ b/include/memory.h @@ -0,0 +1,29 @@ +/* + * AsterOS Kernel + * Autor: Pavel Kalaš + * Rok: 2026 + * + */ + +/* + * Tento header definuje rozhrani pametoveho subsystému jadra. + * Obsahuje API pro allocator fyzickych stranek, jednoduche heap alokace + * a funkce pro cteni zakladnich statistik o celkove a volne pameti. + */ + +#ifndef ASTER_MEMORY_H +#define ASTER_MEMORY_H + +#include "types.h" + +#define PAGE_SIZE 4096 + +void memory_init(void); +void *page_alloc(void); +void page_free(void *page); +void *kmalloc(usize size); +void kfree(void *ptr); +usize memory_total_pages(void); +usize memory_free_pages(void); + +#endif diff --git a/include/panic.h b/include/panic.h new file mode 100644 index 0000000..b68b702 --- /dev/null +++ b/include/panic.h @@ -0,0 +1,19 @@ +/* + * AsterOS Kernel + * Autor: Pavel Kalaš + * Rok: 2026 + * + */ + +/* + * Tento header deklaruje fatalni panic cestu jadra. + * Funkce panic je urcena pro situace, kdy nelze bezpecne pokracovat, + * proto vypise duvod chyby a prevede system do zastaveneho stavu. + */ + +#ifndef ASTER_PANIC_H +#define ASTER_PANIC_H + +void panic(const char *reason); + +#endif diff --git a/include/printk.h b/include/printk.h new file mode 100644 index 0000000..6bfbe7a --- /dev/null +++ b/include/printk.h @@ -0,0 +1,20 @@ +/* + * AsterOS Kernel + * Autor: Pavel Kalaš + * Rok: 2026 + * + */ + +/* + * Tento soubor definuje rozhrani pro kernelovy diagnosticky vystup. + * Poskytuje jednoduche API pro vypis retezce i formatovany log, + * ktery slouzi pro ladeni bootu, preruseni i behu kernel sluzeb. + */ + +#ifndef ASTER_PRINTK_H +#define ASTER_PRINTK_H + +void aster_print(const char *text); +void printk(const char *fmt, ...); + +#endif diff --git a/include/process.h b/include/process.h new file mode 100644 index 0000000..c719458 --- /dev/null +++ b/include/process.h @@ -0,0 +1,57 @@ +/* + * AsterOS Kernel + * Autor: Pavel Kalaš + * Rok: 2026 + * + */ + +/* + * Tento header popisuje procesovy model jadra. + * Obsahuje stavy procesu, kontext CPU registru a strukturu process_t, + * plus API pro vytvareni procesu, ukonceni a pristup k procesove tabulce. + */ + +#ifndef ASTER_PROCESS_H +#define ASTER_PROCESS_H + +#include "types.h" + +#define ASTER_MAX_PROCESSES 16 + +typedef enum { + PROCESS_UNUSED = 0, + PROCESS_READY, + PROCESS_RUNNING, + PROCESS_BLOCKED, + PROCESS_EXITED +} process_state_t; + +typedef struct { + u64 r15; + u64 r14; + u64 r13; + u64 r12; + u64 rbx; + u64 rbp; + u64 rip; +} context_t; + +typedef struct { + u32 pid; + process_state_t state; + u8 priority; + u8 *stack_base; + u64 stack_top; + context_t context; + const char *name; +} process_t; + +void process_init(void); +process_t *process_create(const char *name, void (*entry)(void), u8 priority); +void process_exit(void); +process_t *process_current(void); +void process_set_current(process_t *p); +process_t *process_table(void); +usize process_count(void); + +#endif diff --git a/include/scheduler.h b/include/scheduler.h new file mode 100644 index 0000000..0ee06be --- /dev/null +++ b/include/scheduler.h @@ -0,0 +1,25 @@ +/* + * AsterOS Kernel + * Autor: Pavel Kalaš + * Rok: 2026 + * + */ + +/* + * Tento soubor deklaruje planovac, ktery rozhoduje o prideleni CPU casu. + * Popisuje vstupni body pro inicializaci, periodicky tick a prepnuti, + * a funkce pro vyber dalsiho kandidata k vykonu. + */ + +#ifndef ASTER_SCHEDULER_H +#define ASTER_SCHEDULER_H + +#include "process.h" + +void scheduler_init(void); +void scheduler_tick(void); +void scheduler_run_once(void); +void scheduler_yield(void); +process_t *scheduler_pick_next(void); + +#endif diff --git a/include/storage.h b/include/storage.h new file mode 100644 index 0000000..155ba62 --- /dev/null +++ b/include/storage.h @@ -0,0 +1,41 @@ +/* + * AsterOS Kernel + * Autor: Pavel Kalaš + * Rok: 2026 + * + */ + +/* + * Tento header definuje verejne rozhrani jednoducheho AsterFS. + * Popisuje format uzlu souboru nebo adresare a deklaruje operace + * pro inicializaci, vytvareni, cteni, zapis a vypis obsahu. + */ + +#ifndef ASTER_STORAGE_H +#define ASTER_STORAGE_H + +#include "types.h" + +#define ASTERFS_MAX_FILES 64 +#define ASTERFS_NAME_LEN 64 +#define ASTERFS_DATA_LEN 512 + +typedef struct { + char name[ASTERFS_NAME_LEN]; + u8 is_dir; + u16 size; + u8 data[ASTERFS_DATA_LEN]; +} asterfs_node_t; + +void asterfs_init(void); +int asterfs_create_file(const char *name); +int asterfs_create_dir(const char *name); +int asterfs_remove_file(const char *name); +int asterfs_remove_dir(const char *name); +int asterfs_write_file(const char *name, const u8 *data, u16 len); +int asterfs_read_file(const char *name, u8 *out, u16 max_len); +int asterfs_get_type(const char *name); +void asterfs_list(void (*cb)(const char *name, u8 is_dir, u16 size)); +void asterfs_list_dir(const char *path, void (*cb)(const char *name, u8 is_dir, u16 size)); + +#endif diff --git a/include/string.h b/include/string.h new file mode 100644 index 0000000..8e4b450 --- /dev/null +++ b/include/string.h @@ -0,0 +1,25 @@ +/* + * AsterOS Kernel + * Autor: Pavel Kalaš + * Rok: 2026 + * + */ + +/* + * Tento soubor deklaruje minimalisticke utility bez zavislosti na libc. + * Obsahuje zakladni operace nad retezci a pameti, ktere kernel potrebuje + * uz v ranych fazich bootu, kdy standardni knihovny nejsou dostupne. + */ + +#ifndef ASTER_STRING_H +#define ASTER_STRING_H + +#include "types.h" + +usize aster_strlen(const char *s); +int aster_strcmp(const char *a, const char *b); +int aster_strncmp(const char *a, const char *b, usize n); +void *aster_memcpy(void *dst, const void *src, usize n); +void *aster_memset(void *dst, int v, usize n); + +#endif diff --git a/include/syscall.h b/include/syscall.h new file mode 100644 index 0000000..737cca1 --- /dev/null +++ b/include/syscall.h @@ -0,0 +1,30 @@ +/* + * AsterOS Kernel + * Autor: Pavel Kalaš + * Rok: 2026 + * + */ + +/* + * Tento header popisuje syscall ABI mezi user casti a jadrem. + * Definuje cisla volani, centralni dispatcher a obalove funkce, + * ktere mapuji uzivatelske pozadavky na kernelove sluzby. + */ + +#ifndef ASTER_SYSCALL_H +#define ASTER_SYSCALL_H + +#include "types.h" + +#define SYSCALL_WRITE 0 +#define SYSCALL_ALLOC 1 +#define SYSCALL_PROCESS_CREATE 2 + +void syscall_init(void); +u64 syscall_dispatch(u64 id, u64 a1, u64 a2, u64 a3, u64 a4); + +u64 aster_write(const char *text); +void *aster_alloc(usize size); +i64 aster_process_create(void (*entry)(void), const char *name, u8 priority); + +#endif diff --git a/include/timer.h b/include/timer.h new file mode 100644 index 0000000..931511b --- /dev/null +++ b/include/timer.h @@ -0,0 +1,22 @@ +/* + * AsterOS Kernel + * Autor: Pavel Kalaš + * Rok: 2026 + * + */ + +/* + * Tento soubor deklaruje casovaci vrstvu nad PIT hardwarem. + * Zahrnuje inicializaci frekvence, cteni tiku, inkrement z ISR + * a blokujici uspani jadra na zadany pocet milisekund. + */ + +#ifndef ASTER_TIMER_H +#define ASTER_TIMER_H + +void timer_init(unsigned int hz); +unsigned long timer_ticks(void); +void timer_tick_advance(void); +void timer_sleep_ms(unsigned long ms); + +#endif diff --git a/include/types.h b/include/types.h new file mode 100644 index 0000000..8d0b171 --- /dev/null +++ b/include/types.h @@ -0,0 +1,28 @@ +/* + * AsterOS Kernel + * Autor: Pavel Kalaš + * Rok: 2026 + * + */ + +/* + * Tento header sjednocuje zakladni celoctiselne typy jadra. + * Cilem je mit stabilni velikosti typu nezavisle na kompilatoru + * a pouzivat je konzistentne ve vsech kernel subsystémech. + */ + +#ifndef ASTER_TYPES_H +#define ASTER_TYPES_H + +typedef unsigned long long u64; +typedef signed long long i64; +typedef unsigned int u32; +typedef signed int i32; +typedef unsigned short u16; +typedef signed short i16; +typedef unsigned char u8; +typedef signed char i8; +typedef unsigned long usize; +typedef long isize; + +#endif diff --git a/kernel/aster_api.c b/kernel/aster_api.c new file mode 100644 index 0000000..725c489 --- /dev/null +++ b/kernel/aster_api.c @@ -0,0 +1,56 @@ +/* + * AsterOS Kernel + * Autor: Pavel Kalas + * Rok: 2026 + * + */ + +/* + * Tento soubor implementuje bridge mezi API pro aplikace a jadrem. + * Dnes funguje jako tenka vrstva nad kernel sluzbami a syscall API, + * aby bylo mozne na stejnem rozhrani pozdeji stavet user-space runtime. + */ + +#include "aster_api.h" +#include "display.h" +#include "storage.h" +#include "syscall.h" +#include "timer.h" + +int aster_api_print(const char *text) { + return (int)aster_write(text); +} + +int aster_api_clear(void) { + display_clear(); + return 0; +} + +int aster_api_ticks(u64 *out_ticks) { + if (!out_ticks) { + return -1; + } + + *out_ticks = (u64)timer_ticks(); + return 0; +} + +void *aster_api_alloc(usize size) { + return aster_alloc(size); +} + +int aster_api_file_create(const char *path) { + return asterfs_create_file(path); +} + +int aster_api_file_read(const char *path, u8 *out, u16 max_len) { + return asterfs_read_file(path, out, max_len); +} + +int aster_api_file_write(const char *path, const u8 *data, u16 len) { + return asterfs_write_file(path, data, len); +} + +int aster_api_process_spawn(void (*entry)(void), const char *name, u8 priority) { + return (int)aster_process_create(entry, name, priority); +} diff --git a/kernel/main.c b/kernel/main.c new file mode 100644 index 0000000..0854c9b --- /dev/null +++ b/kernel/main.c @@ -0,0 +1,2959 @@ +/* + * AsterOS Kernel + * Autor: Pavel Kalas + * Rok: 2026 + * + */ + +/* + * Tento soubor je hlavni orchestrator startu celeho jadra. + * Postupne inicializuje CPU, pamet, procesy, ovladace a preruseni, + * vypisuje boot stav, zobrazi banner a preda rizeni shell smycce. + */ + +#include "cpu.h" +#include "display.h" +#include "drivers.h" +#include "memory.h" +#include "printk.h" +#include "process.h" +#include "scheduler.h" +#include "storage.h" +#include "string.h" +#include "syscall.h" +#include "timer.h" + +static char g_cwd[ASTERFS_NAME_LEN] = "/"; +static unsigned long g_timer_hz = 100UL; + +#define FS_TMP_MAX ASTERFS_MAX_FILES +#define FM_MAX_ENTRIES ASTERFS_MAX_FILES +#define FM_VIEW_ROWS 18 +#define SCREEN_W 80 +#define SCREEN_H 25 +#define AUTH_USERS_FILE "/.users" +#define ALIASES_FILE "/.aliases" +#define INSTALL_FLAG_FILE "/.installed" +#define AUTH_MAX_USERS 16 +#define AUTH_NAME_LEN 32 +#define AUTH_PASS_LEN 32 + +static char g_fs_tmp_paths[FS_TMP_MAX][ASTERFS_NAME_LEN]; +static u8 g_fs_tmp_types[FS_TMP_MAX]; +static int g_fs_tmp_count = 0; + +typedef struct { + char name[AUTH_NAME_LEN]; + char pass[AUTH_PASS_LEN]; +} auth_user_t; + +static auth_user_t g_users[AUTH_MAX_USERS]; +static int g_user_count = 0; +static char g_current_user[AUTH_NAME_LEN] = ""; + +static void trim_inplace(char *s); +static void resolve_path_from(const char *base, const char *name, char *out, usize out_size); +static const char *path_basename(const char *path); +static int fs_remove_tree_abs(const char *root); +static void shell_edit_file(const char *path); +static int auth_save_users(void); +static void auth_readline_plain(char *out, int max_len); +static void auth_readline_secret(char *out, int max_len); +static void auth_clear_users(void); +static int auth_add_user(const char *name, const char *pass); +static char g_fm_names[FM_MAX_ENTRIES][ASTERFS_NAME_LEN]; +static u8 g_fm_types[FM_MAX_ENTRIES]; +static u16 g_fm_sizes[FM_MAX_ENTRIES]; +static int g_fm_count = 0; +static char g_status_marquee[160]; +static int g_status_marquee_on = 0; +static usize g_status_marquee_offset = 0; +static int g_status_marquee_only = 0; +static char g_status_left_hint[80]; +static int g_status_left_hint_on = 0; +static int g_boot_splash_active = 0; +static unsigned int g_boot_splash_steps_total = 0; +static unsigned int g_boot_splash_steps_done = 0; +static char g_boot_splash_log[4][56]; +static unsigned int g_boot_splash_log_count = 0; + +static unsigned int cpu_usage_percent(void) { + process_t *table = process_table(); + usize count = process_count(); + usize active = 0; + usize i; + + if (count == 0) { + return 0; + } + + for (i = 0; i < count; ++i) { + if (table[i].state == PROCESS_READY || table[i].state == PROCESS_RUNNING) { + ++active; + } + } + + return (unsigned int)((active * 100U) / count); +} + +static usize append_text(char *dst, usize pos, usize max, const char *src) { + while (*src && pos + 1 < max) { + dst[pos++] = *src++; + } + if (max > 0) { + dst[pos < max ? pos : max - 1] = '\0'; + } + return pos; +} + +static usize append_uint(char *dst, usize pos, usize max, unsigned int v) { + char tmp[12]; + int i = 0; + + if (v == 0) { + if (pos + 1 < max) { + dst[pos++] = '0'; + dst[pos] = '\0'; + } + return pos; + } + + while (v > 0 && i < (int)sizeof(tmp)) { + tmp[i++] = (char)('0' + (v % 10)); + v /= 10; + } + + while (i > 0 && pos + 1 < max) { + dst[pos++] = tmp[--i]; + } + + dst[pos] = '\0'; + return pos; +} + +static void status_set_marquee(const char *text) { + usize p = 0; + usize i = 0; + + if (!text) { + g_status_marquee[0] = '\0'; + g_status_marquee_on = 0; + g_status_marquee_offset = 0; + return; + } + + while (text[i] && p + 1 < sizeof(g_status_marquee)) { + g_status_marquee[p++] = text[i++]; + } + + /* Gap so looped marquee does not stick to itself. */ + for (i = 0; i < 10 && p + 1 < sizeof(g_status_marquee); ++i) { + g_status_marquee[p++] = ' '; + } + + g_status_marquee[p] = '\0'; + g_status_marquee_on = (p > 0) ? 1 : 0; + g_status_marquee_offset = 0; +} + +static void status_clear_marquee(void) { + g_status_marquee[0] = '\0'; + g_status_marquee_on = 0; + g_status_marquee_offset = 0; +} + +static void status_set_left_hint(const char *text) { + usize i = 0; + + if (!text) { + g_status_left_hint[0] = '\0'; + g_status_left_hint_on = 0; + return; + } + + while (text[i] && i + 1 < sizeof(g_status_left_hint)) { + g_status_left_hint[i] = text[i]; + ++i; + } + g_status_left_hint[i] = '\0'; + g_status_left_hint_on = (i > 0) ? 1 : 0; +} + +static void status_clear_left_hint(void) { + g_status_left_hint[0] = '\0'; + g_status_left_hint_on = 0; +} + +static void render_shell_statusbar(void) { + usize save_row; + usize save_col; + usize total = memory_total_pages(); + usize freep = memory_free_pages(); + usize used = total >= freep ? total - freep : 0; + unsigned int cpu = cpu_usage_percent(); + char left[64]; + char right[64]; + char middle[64]; + usize rlen; + usize llen; + usize mlen; + usize i; + usize right_start; + usize middle_start; + + display_get_cursor(&save_row, &save_col); + + left[0] = '\0'; + right[0] = '\0'; + middle[0] = '\0'; + + display_fill_row(SCREEN_H - 1, ' ', 0x0E, 0x08); + + if (!g_status_marquee_only) { + usize p = 0; + p = append_text(left, p, sizeof(left), "pamet: pages="); + p = append_uint(left, p, sizeof(left), (unsigned int)total); + p = append_text(left, p, sizeof(left), " volne="); + p = append_uint(left, p, sizeof(left), (unsigned int)freep); + p = append_text(left, p, sizeof(left), " pouzite="); + p = append_uint(left, p, sizeof(left), (unsigned int)used); + (void)p; + display_write_at(SCREEN_H - 1, 0, left, 0x0E, 0x08); + } else if (g_status_left_hint_on) { + display_write_at(SCREEN_H - 1, 0, g_status_left_hint, 0x0E, 0x08); + llen = 0; + while (g_status_left_hint[llen]) { + ++llen; + } + } + + /* Right aligned label. */ + right[0] = '\0'; + if (!g_status_marquee_only) { + const char *prefix = "cpu usage: "; + usize p = 0; + while (prefix[p] && p < sizeof(right) - 1) { + right[p] = prefix[p]; + ++p; + } + p = append_uint(right, p, sizeof(right), cpu); + if (p + 1 < sizeof(right)) { + right[p++] = '%'; + } + right[p] = '\0'; + } + + rlen = 0; + while (right[rlen]) ++rlen; + if (!g_status_marquee_only) { + llen = 0; + while (left[llen]) ++llen; + } + + if (g_status_marquee_on) { + usize src_len = 0; + usize view_len = 22; + usize s; + while (g_status_marquee[src_len]) { + ++src_len; + } + if (view_len + 1 > sizeof(middle)) { + view_len = sizeof(middle) - 1; + } + if (src_len > 0) { + s = g_status_marquee_offset % src_len; + for (i = 0; i < view_len; ++i) { + middle[i] = g_status_marquee[(s + i) % src_len]; + } + middle[view_len] = '\0'; + } + } + + mlen = 0; + while (middle[mlen]) ++mlen; + if (mlen > 0) { + middle_start = (SCREEN_W > mlen) ? ((SCREEN_W - mlen) / 2) : 0; + if (g_status_marquee_only) { + if (middle_start < llen + 6) { + middle_start = llen + 6; + } + } else if (middle_start < llen + 1) { + middle_start = llen + 1; + } + if (middle_start + mlen + 1 < SCREEN_W) { + display_write_at(SCREEN_H - 1, middle_start, middle, 0x0E, 0x08); + } + } + + if (!g_status_marquee_only) { + right_start = (rlen < SCREEN_W) ? (SCREEN_W - rlen) : 0; + display_write_at(SCREEN_H - 1, right_start, right, 0x0E, 0x08); + } + + if (save_row >= SCREEN_H - 1) { + save_row = SCREEN_H - 2; + } + display_set_cursor(save_row, save_col); +} + +static inline void outb(u16 port, u8 value) { + __asm__ volatile ("outb %0, %1" : : "a"(value), "Nd"(port)); +} + +static inline void outw(u16 port, u16 value) { + __asm__ volatile ("outw %0, %1" : : "a"(value), "Nd"(port)); +} + +static int kbc_wait_input_clear(void) { + unsigned int i; + for (i = 0; i < 100000U; ++i) { + u8 s; + __asm__ volatile ("inb %1, %0" : "=a"(s) : "Nd"((u16)0x64)); + if ((s & 0x02U) == 0U) { + return 1; + } + } + return 0; +} + +static void print_error(const char *msg) { + display_set_color(0x0C, 0x00); + printk("%s\n", msg); + display_set_color(0x0F, 0x00); +} + +static int str_starts_with(const char *s, const char *prefix) { + while (*prefix) { + if (*s++ != *prefix++) { + return 0; + } + } + return 1; +} + +static void boot_splash_render_log(void) { + unsigned int i; + + display_write_at(13, 20, "Step log (array=4):", 0x0F, 0x00); + for (i = 0; i < 4U; ++i) { + display_write_at(14 + i, 20, " ", 0x0F, 0x00); + if (i < g_boot_splash_log_count) { + display_write_at(14 + i, 20, g_boot_splash_log[i], 0x0F, 0x00); + } + } +} + +static void boot_splash_push_log(const char *text) { + unsigned int i; + usize n = 0; + + if (!text) { + return; + } + + if (g_boot_splash_log_count < 4U) { + i = g_boot_splash_log_count; + ++g_boot_splash_log_count; + } else { + for (i = 1; i < 4U; ++i) { + aster_memcpy(g_boot_splash_log[i - 1], g_boot_splash_log[i], sizeof(g_boot_splash_log[0])); + } + i = 3U; + } + + while (text[n] && n + 1 < sizeof(g_boot_splash_log[0])) { + g_boot_splash_log[i][n] = text[n]; + ++n; + } + g_boot_splash_log[i][n] = '\0'; + + boot_splash_render_log(); +} + +static void boot_splash_begin(unsigned int total_steps) { + const usize bar_w = 34; + usize i; + char bar[36]; + + if (total_steps == 0) { + total_steps = 1; + } + + g_boot_splash_active = 1; + g_boot_splash_steps_total = total_steps; + g_boot_splash_steps_done = 0; + g_boot_splash_log_count = 0; + aster_memset(g_boot_splash_log, 0, sizeof(g_boot_splash_log)); + + display_set_color(0x0F, 0x00); + display_clear(); + + display_set_color(0x0F, 0x01); + display_write_at(6, 18, "+------------------------------------------+", 0x0F, 0x01); + display_write_at(7, 18, "| ASTEROS CORE |", 0x0F, 0x01); + display_write_at(8, 18, "| booting user environment |", 0x0F, 0x01); + display_write_at(9, 18, "+------------------------------------------+", 0x0F, 0x01); + display_set_color(0x0F, 0x00); + + display_write_at(12, 20, "Loading modules...", 0x0F, 0x00); + + for (i = 0; i < bar_w; ++i) { + bar[i] = '-'; + } + bar[bar_w] = '\0'; + + display_write_at(18, 20, "[", 0x0E, 0x00); + display_write_at(18, 21, bar, 0x0A, 0x00); + display_write_at(18, 21 + bar_w, "]", 0x0E, 0x00); + display_write_at(19, 34, "0%", 0x0F, 0x00); + + boot_splash_render_log(); +} + +static void boot_splash_update(const char *label, const char *state, u8 state_color) { + const usize bar_w = 34; + char bar[36]; + char log_line[56]; + char pct[8]; + unsigned int i; + unsigned int percent; + usize fill; + usize p = 0; + + if (!g_boot_splash_active) { + return; + } + + (void)state_color; + + if (g_boot_splash_steps_done < g_boot_splash_steps_total) { + ++g_boot_splash_steps_done; + } + + p = 0; + p = append_text(log_line, p, sizeof(log_line), "["); + p = append_text(log_line, p, sizeof(log_line), state ? state : "?"); + p = append_text(log_line, p, sizeof(log_line), "] "); + p = append_text(log_line, p, sizeof(log_line), label ? label : "unknown"); + log_line[p] = '\0'; + boot_splash_push_log(log_line); + + percent = (g_boot_splash_steps_done * 100U) / g_boot_splash_steps_total; + fill = (usize)((g_boot_splash_steps_done * bar_w) / g_boot_splash_steps_total); + + for (i = 0; i < bar_w; ++i) { + bar[i] = ((usize)i < fill) ? '#' : '-'; + } + bar[bar_w] = '\0'; + + display_write_at(18, 21, bar, 0x0A, 0x00); + + p = 0; + p = append_uint(pct, p, sizeof(pct), percent); + if (p + 1 < sizeof(pct)) { + pct[p++] = '%'; + pct[p] = '\0'; + } + + display_write_at(19, 34, " ", 0x0F, 0x00); + display_write_at(19, 34, pct, 0x0F, 0x00); +} + +static void boot_splash_end(void) { + g_boot_splash_active = 0; +} + +static void boot_step_begin(const char *label) { + if (g_boot_splash_active) { + char log_line[56]; + usize p = 0; + p = append_text(log_line, p, sizeof(log_line), "[ ... ] "); + p = append_text(log_line, p, sizeof(log_line), label ? label : "unknown"); + log_line[p] = '\0'; + boot_splash_push_log(log_line); + return; + } + display_set_color(0x0F, 0x00); + printk("[ ... ] %s", label); +} + +static void boot_step_finish(const char *label, const char *state, u8 state_color) { + if (g_boot_splash_active) { + boot_splash_update(label, state, state_color); + return; + } + aster_print("\r"); + display_set_color(0x0F, 0x00); + aster_print("[ "); + display_set_color(state_color, 0x00); + aster_print(state); + display_set_color(0x0F, 0x00); + printk(" ] %s\n", label); +} + +static void boot_step_ok(const char *label) { + boot_step_finish(label, "OK", 0x0A); +} + +static void boot_step_skip(const char *label) { + boot_step_finish(label, "SKIP", 0x0E); +} + +static int alias_name_equals(const char *start, usize len, const char *cmd) { + usize i = 0; + while (cmd[i] && i < len) { + if (start[i] != cmd[i]) { + return 0; + } + ++i; + } + return i == len && cmd[i] == '\0'; +} + +static int alias_lookup(const char *cmd, char *out, usize out_size) { + char buf[ASTERFS_DATA_LEN + 1]; + int n = asterfs_read_file(ALIASES_FILE, (u8 *)buf, ASTERFS_DATA_LEN); + usize i = 0; + + if (!cmd || !out || out_size == 0 || n < 0) { + return 0; + } + + buf[n] = '\0'; + while (i < (usize)n) { + usize line_start = i; + usize name_len = 0; + usize val_start = 0; + usize val_end = 0; + usize p = 0; + + while (i < (usize)n && buf[i] != '\n') { + ++i; + } + + p = line_start; + while (p < (usize)n && (buf[p] == ' ' || buf[p] == '\t')) { + ++p; + } + + if (p < (usize)n && buf[p] != '#' && buf[p] != '\n') { + usize name_start = p; + while (p < (usize)n && buf[p] != '=' && buf[p] != '\n') { + ++p; + } + + if (p < (usize)n && buf[p] == '=') { + name_len = p - name_start; + val_start = p + 1; + val_end = i; + + while (name_len > 0 && (buf[name_start + name_len - 1] == ' ' || buf[name_start + name_len - 1] == '\t')) { + --name_len; + } + while (val_start < val_end && (buf[val_start] == ' ' || buf[val_start] == '\t')) { + ++val_start; + } + while (val_end > val_start && (buf[val_end - 1] == ' ' || buf[val_end - 1] == '\t' || buf[val_end - 1] == '\r')) { + --val_end; + } + + if (name_len > 0 && val_end > val_start && alias_name_equals(&buf[name_start], name_len, cmd)) { + usize out_len = val_end - val_start; + if (out_len + 1 > out_size) { + out_len = out_size - 1; + } + aster_memcpy(out, &buf[val_start], out_len); + out[out_len] = '\0'; + return out_len > 0 ? 1 : 0; + } + } + } + + if (i < (usize)n && buf[i] == '\n') { + ++i; + } + } + + return 0; +} + +static void ensure_aliases_file(void) { + static const char defaults[] = + "ver=info\n" + "mem=memory\n" + "ps=process\n" + "cls=clear\n" + "dir=ls\n" + "md=makdir\n" + "rd=remdir\n" + "touch=makfile\n" + "del=remfile\n" + "copy=copfile\n" + "move=movfile\n" + "read=cat\n" + "type=cat\n" + "halt=shutdown\n"; + + if (asterfs_get_type(ALIASES_FILE) >= 0) { + return; + } + + if (asterfs_create_file(ALIASES_FILE) != 0) { + return; + } + + (void)asterfs_write_file(ALIASES_FILE, (const u8 *)defaults, (u16)(sizeof(defaults) - 1)); +} + +static int fs_ensure_dir(const char *path) { + int t = asterfs_get_type(path); + + if (!path || path[0] == '\0') { + return -1; + } + if (t == 1) { + return 0; + } + if (t == 0) { + return -1; + } + return asterfs_create_dir(path); +} + +static int fs_ensure_file_text(const char *path, const char *text) { + int t = asterfs_get_type(path); + + if (!path || !text || path[0] == '\0') { + return -1; + } + if (t == 1) { + return -1; + } + if (t < 0 && asterfs_create_file(path) != 0) { + return -1; + } + + return asterfs_write_file(path, (const u8 *)text, (u16)aster_strlen(text)) < 0 ? -1 : 0; +} + +static int system_is_installed(void) { + return asterfs_get_type(INSTALL_FLAG_FILE) == 0; +} + +static void cmd_setup_install(void) { + static const char *dirs[] = { + "/etc", + "/home", + "/bin", + "/var", + "/tmp" + }; + static const char readme[] = + "AsterOS base install\n" + "--------------------\n" + "System byl nainstalovan prikazem setup.\n" + "Pro napovedu pouzij: help\n" + "Aliasy upravis v: /.aliases\n"; + static const char motd[] = + "Welcome to AsterOS core v0.1\n" + "Type 'help' for commands.\n"; + static const char profile[] = + "echo Welcome in AsterOS\n"; + static const char installed[] = + "installed=1\n"; + char setup_user[AUTH_NAME_LEN]; + char setup_pass[AUTH_PASS_LEN]; + char user_home[ASTERFS_NAME_LEN]; + usize p = 0; + usize i; + + aster_print("Setup: instalace systemu na AsterFS disk...\n"); + + for (;;) { + aster_print("Setup uzivatel: "); + auth_readline_plain(setup_user, sizeof(setup_user)); + if (setup_user[0] == '\0') { + print_error("Uzivatel nesmi byt prazdny"); + continue; + } + break; + } + + aster_print("Setup heslo (prazdne = auto-login): "); + auth_readline_secret(setup_pass, sizeof(setup_pass)); + + auth_clear_users(); + if (auth_add_user(setup_user, setup_pass) != 0 || auth_save_users() != 0) { + print_error("Setup error: nelze ulozit uzivatele"); + return; + } + + aster_memset(g_current_user, 0, sizeof(g_current_user)); + aster_memcpy(g_current_user, setup_user, aster_strlen(setup_user)); + + for (i = 0; i < sizeof(dirs) / sizeof(dirs[0]); ++i) { + if (fs_ensure_dir(dirs[i]) != 0) { + printk("Setup error: nelze pripravit slozku %s\n", dirs[i]); + return; + } + } + + if (setup_user[0]) { + p = append_text(user_home, p, sizeof(user_home), "/home/"); + p = append_text(user_home, p, sizeof(user_home), setup_user); + user_home[p] = '\0'; + + if (fs_ensure_dir(user_home) != 0) { + printk("Setup error: nelze pripravit home %s\n", user_home); + return; + } + } + + if (fs_ensure_file_text("/README.txt", readme) != 0) { + print_error("Setup error: README"); + return; + } + + if (fs_ensure_file_text("/etc/motd", motd) != 0) { + print_error("Setup error: /etc/motd"); + return; + } + + if (fs_ensure_file_text("/etc/profile", profile) != 0) { + print_error("Setup error: /etc/profile"); + return; + } + + if (fs_ensure_file_text(INSTALL_FLAG_FILE, installed) != 0) { + print_error("Setup error: /.installed"); + return; + } + + ensure_aliases_file(); + + aster_print("Setup complete: system nainstalovan na disk\n"); +} + +static void show_welcome_banner(void) { + display_set_color(0x0F, 0x01); + aster_print("\n"); + aster_print("========================================\n"); + aster_print(" ASTEROS KERNEL \n"); + aster_print(" Autor: Pavel Kalas \n"); + aster_print(" Rok: 2026 \n"); + aster_print("========================================\n"); + aster_print("\n"); + display_set_color(0x0F, 0x00); +} + +static void show_aster_banner(const char *subtitle) { + display_set_color(0x0F, 0x01); + aster_print("+------------------------------------------------+"); + aster_print("\n"); + aster_print("| AsterOS core v0.1 |"); + aster_print("\n"); + aster_print("| author: Pavel Kalas |"); + aster_print("\n"); + aster_print("| text mode shell system |"); + aster_print("\n"); + aster_print("+------------------------------------------------+"); + aster_print("\n"); + display_set_color(0x0F, 0x00); + + if (subtitle && subtitle[0]) { + printk("%s\n", subtitle); + } +} + +static void print_exec_profile_ticks(const void *entry_addr, unsigned long delta_ticks) { + unsigned long long scaled = 0; + unsigned int ms_int = 0; + unsigned int ms_frac7 = 0; + char frac[8]; + int i; + + if (g_timer_hz != 0) { + scaled = ((unsigned long long)delta_ticks * 10000000000ULL) / (unsigned long long)g_timer_hz; + ms_int = (unsigned int)(scaled / 10000000ULL); + ms_frac7 = (unsigned int)(scaled % 10000000ULL); + } + + frac[7] = '\0'; + for (i = 6; i >= 0; --i) { + frac[i] = (char)('0' + (ms_frac7 % 10U)); + ms_frac7 /= 10U; + } + + printk("%u.%sms - %p\n", ms_int, frac, entry_addr); +} + +static void print_exec_profile(const void *entry_addr, unsigned long start_ticks) { + unsigned long end_ticks = timer_ticks(); + unsigned long delta_ticks = end_ticks - start_ticks; + print_exec_profile_ticks(entry_addr, delta_ticks); +} + +static const char *g_help_lines[] = { + "help [1|2|3] - napoveda po strankach", + "helpall - interaktivni help Enter/Q", + "info - informace o jadru", + "memory|mem - stav pameti", + "process|ps - seznam procesu", + "clear|cls - vycistit obrazovku", + "ls - vypis aktualni slozky", + "cd filename - zmena slozky", + "makdir filename - vytvorit slozku", + "remdir filename - smazat slozku", + "movdir src dst [-rec] - presun slozky", + "copdir src dst [-rec] - kopie slozky", + "makfile filename - vytvorit soubor", + "remfile filename - smazat soubor", + "movfile src dst - presun souboru", + "copfile src dst - kopie souboru", + "cat|read filename - obsah souboru", + "write filename text - zapis textu", + "setup - instalace systemu na disk", + "asrun filename - spustit AsterScript", + "fm - file manager", + "./filename - C-like script printf", + "edit filename - editor (Ctrl+S, ESC)", + "calc A op B - vypocet (+ - * /)", + "echo text - vypis textu", + "ticks - pocet tiknuti casovace", + "alloc N - alokovat N bajtu", + "useradd user [pass] - vytvori uzivatele", + "passwdch [user] - zmeni heslo uzivatele", + "exit - odhlaseni do loginu", + "reboot - restart systemu", + "shutdown|halt - zastavit system", + "aliasy - uprav v /.aliases" +}; + +static void shell_help_page(unsigned int page) { + const unsigned int per_page = 11; + const unsigned int total = (unsigned int)(sizeof(g_help_lines) / sizeof(g_help_lines[0])); + const unsigned int pages = (total + per_page - 1U) / per_page; + unsigned int start; + unsigned int end; + unsigned int i; + + if (page < 1U || page > pages) { + printk("Neplatna stranka. Pouzij 1..%u\n", pages); + return; + } + + start = (page - 1U) * per_page; + end = start + per_page; + if (end > total) { + end = total; + } + + printk("Help stranka %u/%u\n", page, pages); + for (i = start; i < end; ++i) { + printk(" %s\n", g_help_lines[i]); + } + aster_print("Tip: pouzij help 1, help 2, help 3 nebo helpall\n"); +} + +static void shell_help_all(void) { + unsigned int i; + unsigned int total = (unsigned int)(sizeof(g_help_lines) / sizeof(g_help_lines[0])); + + aster_print("helpall: Enter = dalsi radek, Q = konec\n"); + for (i = 0; i < total; ++i) { + int k; + printk(" %s\n", g_help_lines[i]); + + for (;;) { + k = keyboard_read_key(); + if (k == '\n') { + break; + } + if (k == 'q' || k == 'Q' || k == 27) { + aster_print("helpall ukoncen\n"); + return; + } + } + } + + aster_print("helpall hotovo\n"); +} + +static void auth_readline_plain(char *out, int max_len) { + keyboard_readline(out, max_len); + trim_inplace(out); +} + +static void auth_readline_secret(char *out, int max_len) { + int i = 0; + + if (max_len <= 1) { + out[0] = '\0'; + return; + } + + for (;;) { + int c = keyboard_read_key(); + + if (c == '\n') { + display_putc('\n'); + out[i] = '\0'; + return; + } + + if (c == '\b') { + if (i > 0) { + --i; + } + continue; + } + + if (c >= 32 && c <= 126 && i < max_len - 1) { + out[i++] = (char)c; + } + } +} + +static void auth_clear_users(void) { + int i; + + for (i = 0; i < AUTH_MAX_USERS; ++i) { + g_users[i].name[0] = '\0'; + g_users[i].pass[0] = '\0'; + } + g_user_count = 0; +} + +static int auth_find_user(const char *name) { + int i; + for (i = 0; i < g_user_count; ++i) { + if (aster_strcmp(g_users[i].name, name) == 0) { + return i; + } + } + return -1; +} + +static int auth_add_user(const char *name, const char *pass) { + usize nlen; + usize plen; + + if (!name || name[0] == '\0' || g_user_count >= AUTH_MAX_USERS) { + return -1; + } + + if (auth_find_user(name) >= 0) { + return -1; + } + + nlen = aster_strlen(name); + plen = pass ? aster_strlen(pass) : 0; + if (nlen >= AUTH_NAME_LEN || plen >= AUTH_PASS_LEN) { + return -1; + } + + aster_memset(g_users[g_user_count].name, 0, AUTH_NAME_LEN); + aster_memset(g_users[g_user_count].pass, 0, AUTH_PASS_LEN); + aster_memcpy(g_users[g_user_count].name, name, nlen); + if (pass) { + aster_memcpy(g_users[g_user_count].pass, pass, plen); + } + + ++g_user_count; + return 0; +} + +static int auth_save_users(void) { + char buf[ASTERFS_DATA_LEN]; + usize pos = 0; + int i; + + aster_memset(buf, 0, sizeof(buf)); + + for (i = 0; i < g_user_count; ++i) { + usize nlen = aster_strlen(g_users[i].name); + usize plen = aster_strlen(g_users[i].pass); + + if (pos + nlen + 1 + plen + 1 >= sizeof(buf)) { + return -1; + } + + aster_memcpy(buf + pos, g_users[i].name, nlen); + pos += nlen; + buf[pos++] = ':'; + aster_memcpy(buf + pos, g_users[i].pass, plen); + pos += plen; + buf[pos++] = '\n'; + } + + return asterfs_write_file(AUTH_USERS_FILE, (const u8 *)buf, (u16)pos) >= 0 ? 0 : -1; +} + +static int auth_load_users(void) { + char buf[ASTERFS_DATA_LEN + 1]; + int n; + usize i = 0; + + auth_clear_users(); + + n = asterfs_read_file(AUTH_USERS_FILE, (u8 *)buf, ASTERFS_DATA_LEN); + if (n < 0) { + return -1; + } + + buf[n] = '\0'; + + while (i < (usize)n) { + char name[AUTH_NAME_LEN]; + char pass[AUTH_PASS_LEN]; + usize ni = 0; + usize pi = 0; + + while (i < (usize)n && (buf[i] == '\n' || buf[i] == '\r')) { + ++i; + } + if (i >= (usize)n) { + break; + } + + while (i < (usize)n && buf[i] != ':' && buf[i] != '\n' && ni < AUTH_NAME_LEN - 1) { + name[ni++] = buf[i++]; + } + name[ni] = '\0'; + + if (i < (usize)n && buf[i] == ':') { + ++i; + } + + while (i < (usize)n && buf[i] != '\n' && pi < AUTH_PASS_LEN - 1) { + pass[pi++] = buf[i++]; + } + pass[pi] = '\0'; + + while (i < (usize)n && buf[i] != '\n') { + ++i; + } + if (i < (usize)n && buf[i] == '\n') { + ++i; + } + + if (name[0] != '\0') { + if (auth_add_user(name, pass) != 0) { + return -1; + } + } + } + + return 0; +} + +static int auth_set_pass(const char *user, const char *pass) { + int idx = auth_find_user(user); + usize plen; + + if (idx < 0) { + return -1; + } + + plen = pass ? aster_strlen(pass) : 0; + if (plen >= AUTH_PASS_LEN) { + return -1; + } + + aster_memset(g_users[idx].pass, 0, AUTH_PASS_LEN); + if (pass) { + aster_memcpy(g_users[idx].pass, pass, plen); + } + return 0; +} + +static int auth_find_autologin_user(void) { + int i; + for (i = 0; i < g_user_count; ++i) { + if (g_users[i].pass[0] == '\0') { + return i; + } + } + return -1; +} + +static void auth_first_setup_if_needed(void) { + char name[AUTH_NAME_LEN]; + char pass[AUTH_PASS_LEN]; + + if (auth_load_users() == 0 && g_user_count > 0) { + return; + } + + display_clear(); + aster_print("=== First Start Setup ===\n"); + aster_print("Vytvor prvniho uzivatele.\n"); + + for (;;) { + aster_print("Uzivatel: "); + auth_readline_plain(name, sizeof(name)); + if (name[0] == '\0') { + print_error("Uzivatel nesmi byt prazdny"); + continue; + } + break; + } + + aster_print("Heslo (prazdne = auto-login): "); + auth_readline_secret(pass, sizeof(pass)); + + auth_clear_users(); + if (auth_add_user(name, pass) != 0 || auth_save_users() != 0) { + print_error("Chyba ulozeni prvniho uzivatele"); + } +} + +static void auth_login_screen(void) { + char name[AUTH_NAME_LEN]; + char pass[AUTH_PASS_LEN]; + int auto_idx; + + if (!system_is_installed()) { + aster_memset(g_current_user, 0, sizeof(g_current_user)); + aster_memcpy(g_current_user, "guest", 5); + return; + } + + for (;;) { + if (auth_load_users() != 0 || g_user_count == 0) { + print_error("Neni vytvoren uzivatel. Spust setup."); + timer_sleep_ms(1000); + aster_memset(g_current_user, 0, sizeof(g_current_user)); + aster_memcpy(g_current_user, "guest", 5); + return; + } + + auto_idx = auth_find_autologin_user(); + if (auto_idx >= 0) { + aster_memset(g_current_user, 0, sizeof(g_current_user)); + aster_memcpy(g_current_user, g_users[auto_idx].name, aster_strlen(g_users[auto_idx].name)); + return; + } + + display_clear(); + show_aster_banner("Login"); + aster_print("Uzivatel: "); + auth_readline_plain(name, sizeof(name)); + aster_print("Heslo: "); + auth_readline_secret(pass, sizeof(pass)); + + { + int idx = auth_find_user(name); + if (idx >= 0 && aster_strcmp(g_users[idx].pass, pass) == 0) { + aster_memset(g_current_user, 0, sizeof(g_current_user)); + aster_memcpy(g_current_user, g_users[idx].name, aster_strlen(g_users[idx].name)); + return; + } + } + + print_error("Spatny login nebo heslo"); + timer_sleep_ms(1000); + } +} + +static int is_space(char c) { + return c == ' ' || c == '\t' || c == '\r' || c == '\n'; +} + +static void trim_inplace(char *s) { + usize start = 0; + usize end = aster_strlen(s); + usize i = 0; + + while (s[start] && is_space(s[start])) { + ++start; + } + + while (end > start && is_space(s[end - 1])) { + --end; + } + + while (start < end) { + s[i++] = s[start++]; + } + + s[i] = '\0'; +} + +static char *next_token(char **cursor) { + char *p = *cursor; + char *start; + + while (*p && is_space(*p)) { + ++p; + } + + if (*p == '\0') { + *cursor = p; + return 0; + } + + start = p; + while (*p && !is_space(*p)) { + ++p; + } + + if (*p) { + *p++ = '\0'; + } + + *cursor = p; + return start; +} + +static unsigned long parse_u32(const char *s, int *ok) { + unsigned long value = 0; + *ok = 0; + + if (!s || *s == '\0') { + return 0; + } + + while (*s) { + if (*s < '0' || *s > '9') { + return 0; + } + + value = value * 10UL + (unsigned long)(*s - '0'); + ++s; + } + + *ok = 1; + return value; +} + +static long parse_i32(const char *s, int *ok) { + int sign = 1; + unsigned long v; + + if (!s || *s == '\0') { + *ok = 0; + return 0; + } + + if (*s == '-') { + sign = -1; + ++s; + } + + v = parse_u32(s, ok); + if (!*ok) { + return 0; + } + + return (long)v * (long)sign; +} + +static void resolve_path(const char *name, char *out, usize out_size) { + usize i = 0; + usize j = 0; + + if (!name || !out || out_size < 2) { + return; + } + + if (name[0] == '/') { + while (name[i] && i < out_size - 1) { + out[i] = name[i]; + ++i; + } + out[i] = '\0'; + return; + } + + if (aster_strcmp(name, ".") == 0) { + resolve_path(g_cwd, out, out_size); + return; + } + + if (aster_strcmp(name, "..") == 0) { + usize n = aster_strlen(g_cwd); + if (n <= 1) { + out[0] = '/'; + out[1] = '\0'; + return; + } + + while (n > 1 && g_cwd[n - 1] != '/') { + --n; + } + + if (n > 1) { + --n; + } + + for (i = 0; i < n && i < out_size - 1; ++i) { + out[i] = g_cwd[i]; + } + + if (i == 0) { + out[i++] = '/'; + } + + out[i] = '\0'; + return; + } + + if (aster_strcmp(g_cwd, "/") == 0) { + out[j++] = '/'; + } else { + while (g_cwd[i] && j < out_size - 1) { + out[j++] = g_cwd[i++]; + } + if (j < out_size - 1) { + out[j++] = '/'; + } + } + + i = 0; + while (name[i] && j < out_size - 1) { + out[j++] = name[i++]; + } + + out[j] = '\0'; +} + +static void resolve_path_from(const char *base, const char *name, char *out, usize out_size) { + usize i = 0; + usize j = 0; + + if (!base || !name || !out || out_size < 2) { + return; + } + + if (name[0] == '/') { + while (name[i] && i < out_size - 1) { + out[i] = name[i]; + ++i; + } + out[i] = '\0'; + return; + } + + if (aster_strcmp(name, ".") == 0) { + resolve_path_from("/", base, out, out_size); + return; + } + + if (aster_strcmp(name, "..") == 0) { + usize n = aster_strlen(base); + if (n <= 1) { + out[0] = '/'; + out[1] = '\0'; + return; + } + + while (n > 1 && base[n - 1] != '/') { + --n; + } + if (n > 1) { + --n; + } + + for (i = 0; i < n && i < out_size - 1; ++i) { + out[i] = base[i]; + } + if (i == 0) { + out[i++] = '/'; + } + out[i] = '\0'; + return; + } + + if (aster_strcmp(base, "/") == 0) { + out[j++] = '/'; + } else { + while (base[i] && j < out_size - 1) { + out[j++] = base[i++]; + } + if (j < out_size - 1) { + out[j++] = '/'; + } + } + + i = 0; + while (name[i] && j < out_size - 1) { + out[j++] = name[i++]; + } + out[j] = '\0'; +} + +static int str_ieq(const char *a, const char *b) { + usize i = 0; + while (a[i] && b[i]) { + char ca = a[i]; + char cb = b[i]; + if (ca >= 'A' && ca <= 'Z') ca = (char)(ca - 'A' + 'a'); + if (cb >= 'A' && cb <= 'Z') cb = (char)(cb - 'A' + 'a'); + if (ca != cb) { + return 0; + } + ++i; + } + return a[i] == '\0' && b[i] == '\0'; +} + +static void run_aster_script(const char *path) { + char src[ASTERFS_DATA_LEN + 1]; + char line[128]; + char script_cwd[ASTERFS_NAME_LEN]; + int n; + usize pos = 0; + + n = asterfs_read_file(path, (u8 *)src, ASTERFS_DATA_LEN); + if (n < 0) { + print_error("AsterScript: soubor nenalezen"); + return; + } + src[n] = '\0'; + + aster_memset(script_cwd, 0, sizeof(script_cwd)); + aster_memcpy(script_cwd, g_cwd, aster_strlen(g_cwd)); + + while (pos < (usize)n) { + usize li = 0; + char *cursor; + char *cmd; + char *arg1; + char *arg2; + + while (pos < (usize)n && src[pos] != '\n' && li < sizeof(line) - 1) { + line[li++] = src[pos++]; + } + if (pos < (usize)n && src[pos] == '\n') { + ++pos; + } + line[li] = '\0'; + + trim_inplace(line); + if (line[0] == '\0' || line[0] == '#') { + continue; + } + + cursor = line; + cmd = next_token(&cursor); + if (!cmd) { + continue; + } + + if (str_ieq(cmd, "print") || str_ieq(cmd, "echo")) { + while (*cursor && is_space(*cursor)) { + ++cursor; + } + printk("%s\n", cursor); + } else if (str_ieq(cmd, "read")) { + char full[ASTERFS_NAME_LEN]; + u8 out[ASTERFS_DATA_LEN + 1]; + int rn; + arg1 = next_token(&cursor); + if (!arg1) { + print_error("AsterScript read: chybi soubor"); + continue; + } + resolve_path_from(script_cwd, arg1, full, sizeof(full)); + rn = asterfs_read_file(full, out, ASTERFS_DATA_LEN); + if (rn < 0) { + print_error("AsterScript read: soubor nenalezen"); + continue; + } + out[rn] = '\0'; + printk("%s\n", (char *)out); + } else if (str_ieq(cmd, "write")) { + char full[ASTERFS_NAME_LEN]; + int wn; + arg1 = next_token(&cursor); + while (*cursor && is_space(*cursor)) { + ++cursor; + } + if (!arg1 || *cursor == '\0') { + print_error("AsterScript write: chybi argumenty"); + continue; + } + resolve_path_from(script_cwd, arg1, full, sizeof(full)); + wn = asterfs_write_file(full, (const u8 *)cursor, (u16)aster_strlen(cursor)); + if (wn < 0) { + print_error("AsterScript write: chyba zapisu"); + } + } else if (str_ieq(cmd, "mkdir")) { + char full[ASTERFS_NAME_LEN]; + arg1 = next_token(&cursor); + if (!arg1) { + continue; + } + resolve_path_from(script_cwd, arg1, full, sizeof(full)); + (void)asterfs_create_dir(full); + } else if (str_ieq(cmd, "mkfile")) { + char full[ASTERFS_NAME_LEN]; + arg1 = next_token(&cursor); + if (!arg1) { + continue; + } + resolve_path_from(script_cwd, arg1, full, sizeof(full)); + (void)asterfs_create_file(full); + } else if (str_ieq(cmd, "rmfile")) { + char full[ASTERFS_NAME_LEN]; + arg1 = next_token(&cursor); + if (!arg1) { + continue; + } + resolve_path_from(script_cwd, arg1, full, sizeof(full)); + (void)asterfs_remove_file(full); + } else if (str_ieq(cmd, "rmdir")) { + char full[ASTERFS_NAME_LEN]; + arg1 = next_token(&cursor); + if (!arg1) { + continue; + } + resolve_path_from(script_cwd, arg1, full, sizeof(full)); + (void)asterfs_remove_dir(full); + } else if (str_ieq(cmd, "cd")) { + char full[ASTERFS_NAME_LEN]; + arg1 = next_token(&cursor); + if (!arg1) { + continue; + } + resolve_path_from(script_cwd, arg1, full, sizeof(full)); + if (asterfs_get_type(full) == 1) { + aster_memset(script_cwd, 0, sizeof(script_cwd)); + aster_memcpy(script_cwd, full, aster_strlen(full)); + } + } else if (str_ieq(cmd, "sleep")) { + int ok; + unsigned long ms; + arg1 = next_token(&cursor); + ms = parse_u32(arg1, &ok); + if (ok) { + timer_sleep_ms(ms); + } + } else if (str_ieq(cmd, "calc")) { + int oka; + int okb; + long a; + long b; + long r; + char op; + arg1 = next_token(&cursor); + arg2 = next_token(&cursor); + { + char *arg3 = next_token(&cursor); + if (!arg1 || !arg2 || !arg3 || aster_strlen(arg2) != 1) { + continue; + } + a = parse_i32(arg1, &oka); + b = parse_i32(arg3, &okb); + if (!oka || !okb) { + continue; + } + op = arg2[0]; + if (op == '+') r = a + b; + else if (op == '-') r = a - b; + else if (op == '*') r = a * b; + else if (op == '/' && b != 0) r = a / b; + else continue; + printk("%d\n", (int)r); + } + } + } +} + +static void fm_collect_cb(const char *name, u8 is_dir, u16 size) { + usize n; + if (g_fm_count >= FM_MAX_ENTRIES) { + return; + } + + n = aster_strlen(name); + if (n >= ASTERFS_NAME_LEN) { + n = ASTERFS_NAME_LEN - 1; + } + + aster_memcpy(g_fm_names[g_fm_count], name, n); + g_fm_names[g_fm_count][n] = '\0'; + g_fm_types[g_fm_count] = is_dir; + g_fm_sizes[g_fm_count] = size; + ++g_fm_count; +} + +static void fm_load_dir(const char *path) { + int i; + g_fm_count = 0; + for (i = 0; i < FM_MAX_ENTRIES; ++i) { + g_fm_names[i][0] = '\0'; + g_fm_types[i] = 0; + g_fm_sizes[i] = 0; + } + asterfs_list_dir(path, fm_collect_cb); +} + +static void fm_preview_file(const char *path) { + u8 buf[ASTERFS_DATA_LEN + 1]; + int n; + + n = asterfs_read_file(path, buf, ASTERFS_DATA_LEN); + display_clear(); + display_set_color(0x07, 0x01); + printk(" FILE PREVIEW: %s ", path); + display_set_color(0x0E, 0x08); + aster_print("\n\n"); + + if (n < 0) { + print_error("Soubor nelze otevrit"); + } else { + int i; + buf[n] = '\0'; + for (i = 0; i < n; ++i) { + display_putc((char)buf[i]); + } + } + + display_set_color(0x0E, 0x08); + aster_print("\n\nStiskni libovolnou klavesu...\n"); + (void)keyboard_read_key(); +} + +static void fm_draw(const char *cwd, int selected, int top) { + int i; + + display_set_color(0x0E, 0x08); + display_clear(); + display_set_color(0x07, 0x01); + printk(" ASTER FILE MANAGER | path: %s ", cwd); + + display_set_color(0x0E, 0x08); + aster_print("\n"); + aster_print(" up/down=scroll, enter=open, e=edit, d=delete, backspace=up, q=quit \n\n"); + + for (i = 0; i < FM_VIEW_ROWS; ++i) { + int idx = top + i; + if (idx >= g_fm_count) { + break; + } + + if (idx == selected) { + display_set_color(0x0F, 0x01); + aster_print("> "); + } else { + display_set_color(0x0E, 0x08); + aster_print(" "); + } + + if (g_fm_types[idx]) { + printk("[DIR ] %s\n", g_fm_names[idx]); + } else { + printk("[FILE] %s (%u B)\n", g_fm_names[idx], (unsigned int)g_fm_sizes[idx]); + } + } + + display_set_color(0x0E, 0x08); + render_shell_statusbar(); +} + +static void fm_set_selection_marquee(const char *cwd, int selected) { + char text[120]; + usize p = 0; + + if (g_fm_count <= 0) { + p = append_text(text, p, sizeof(text), "Prazdna slozka: "); + p = append_text(text, p, sizeof(text), cwd); + text[p] = '\0'; + status_set_marquee(text); + return; + } + + if (selected < 0) { + selected = 0; + } + if (selected >= g_fm_count) { + selected = g_fm_count - 1; + } + + if (g_fm_types[selected]) { + p = append_text(text, p, sizeof(text), "Vybrana slozka: "); + p = append_text(text, p, sizeof(text), g_fm_names[selected]); + p = append_text(text, p, sizeof(text), "/"); + } else { + p = append_text(text, p, sizeof(text), "Vybrany soubor: "); + p = append_text(text, p, sizeof(text), g_fm_names[selected]); + } + + text[p] = '\0'; + status_set_marquee(text); +} + +static void run_file_manager(void) { + char fm_cwd[ASTERFS_NAME_LEN]; + char last_cwd[ASTERFS_NAME_LEN]; + int selected = 0; + int last_selected = -1; + int top = 0; + unsigned long last_anim = timer_ticks(); + unsigned long idle_spin = 0; + unsigned long step_ticks = g_timer_hz / 5UL; + + if (step_ticks == 0) { + step_ticks = 1; + } + + aster_memset(fm_cwd, 0, sizeof(fm_cwd)); + aster_memcpy(fm_cwd, g_cwd, aster_strlen(g_cwd)); + aster_memset(last_cwd, 0, sizeof(last_cwd)); + display_set_color(0x0E, 0x08); + display_clear(); + g_status_marquee_only = 1; + + for (;;) { + char full[ASTERFS_NAME_LEN]; + int key; + unsigned long now; + + fm_load_dir(fm_cwd); + + if (g_fm_count == 0) { + selected = 0; + top = 0; + } else { + if (selected >= g_fm_count) { + selected = g_fm_count - 1; + } + if (selected < 0) { + selected = 0; + } + if (top > selected) { + top = selected; + } + if (selected >= top + FM_VIEW_ROWS) { + top = selected - FM_VIEW_ROWS + 1; + } + } + + if (last_selected != selected || aster_strcmp(last_cwd, fm_cwd) != 0) { + fm_set_selection_marquee(fm_cwd, selected); + aster_memset(last_cwd, 0, sizeof(last_cwd)); + aster_memcpy(last_cwd, fm_cwd, aster_strlen(fm_cwd)); + last_selected = selected; + } + fm_draw(fm_cwd, selected, top); + + for (;;) { + unsigned long delta; + unsigned long steps; + key = keyboard_try_read_key(); + now = timer_ticks(); + + if (now > last_anim) { + delta = now - last_anim; + if (delta >= step_ticks) { + steps = delta / step_ticks; + g_status_marquee_offset += (usize)steps; + last_anim += steps * step_ticks; + render_shell_statusbar(); + } + } + + ++idle_spin; + if (idle_spin >= 230000UL) { + idle_spin = 0; + ++g_status_marquee_offset; + render_shell_statusbar(); + } + + if (key != -1) { + idle_spin = 0; + break; + } + + __asm__ volatile ("pause"); + } + + if (key == 'q' || key == 27) { + break; + } + + if (key == ASTER_KEY_UP) { + if (selected > 0) { + --selected; + if (selected < top) { + --top; + } + } + continue; + } + + if (key == ASTER_KEY_DOWN) { + if (selected + 1 < g_fm_count) { + ++selected; + if (selected >= top + FM_VIEW_ROWS) { + ++top; + } + } + continue; + } + + if (key == '\b') { + resolve_path_from(fm_cwd, "..", full, sizeof(full)); + aster_memset(fm_cwd, 0, sizeof(fm_cwd)); + aster_memcpy(fm_cwd, full, aster_strlen(full)); + selected = 0; + top = 0; + continue; + } + + if (key == '\n' && g_fm_count > 0) { + resolve_path_from(fm_cwd, g_fm_names[selected], full, sizeof(full)); + if (g_fm_types[selected]) { + aster_memset(fm_cwd, 0, sizeof(fm_cwd)); + aster_memcpy(fm_cwd, full, aster_strlen(full)); + selected = 0; + top = 0; + } else { + fm_preview_file(full); + } + continue; + } + + if ((key == 'e' || key == 'E') && g_fm_count > 0) { + resolve_path_from(fm_cwd, g_fm_names[selected], full, sizeof(full)); + if (!g_fm_types[selected]) { + shell_edit_file(full); + } + continue; + } + + if ((key == 'd' || key == 'D') && g_fm_count > 0) { + int confirm; + resolve_path_from(fm_cwd, g_fm_names[selected], full, sizeof(full)); + + display_set_color(0x0F, 0x01); + aster_print("\nSmazat vybranou polozku? [Y/N]: "); + display_set_color(0x0E, 0x08); + confirm = keyboard_read_key(); + display_putc('\n'); + + if (confirm == 'Y' || confirm == 'y') { + if (g_fm_types[selected]) { + if (fs_remove_tree_abs(full) != 0) { + print_error("Delete slozky selhal"); + } + } else { + if (asterfs_remove_file(full) != 0) { + print_error("Delete souboru selhal"); + } + } + } + + continue; + } + } + + aster_memset(g_cwd, 0, sizeof(g_cwd)); + aster_memcpy(g_cwd, fm_cwd, aster_strlen(fm_cwd)); + g_status_marquee_only = 0; + status_clear_marquee(); + display_set_color(0x0F, 0x00); + display_clear(); +} + +static const char *path_basename(const char *path) { + usize i = aster_strlen(path); + + while (i > 0) { + if (path[i - 1] == '/') { + return &path[i]; + } + --i; + } + + return path; +} + +static int path_is_self_or_child(const char *root, const char *path) { + usize n; + + if (!root || !path) { + return 0; + } + + if (aster_strcmp(root, path) == 0) { + return 1; + } + + n = aster_strlen(root); + if (n == 0) { + return 0; + } + + if (aster_strncmp(path, root, n) != 0) { + return 0; + } + + return path[n] == '/'; +} + +static int map_path_prefix(const char *src_root, const char *dst_root, const char *path, char *out, usize out_size) { + usize src_n; + usize dst_n; + usize suffix_n; + + if (!path_is_self_or_child(src_root, path)) { + return -1; + } + + src_n = aster_strlen(src_root); + dst_n = aster_strlen(dst_root); + + if (aster_strcmp(src_root, path) == 0) { + if (dst_n + 1 > out_size) { + return -1; + } + aster_memcpy(out, dst_root, dst_n); + out[dst_n] = '\0'; + return 0; + } + + suffix_n = aster_strlen(path + src_n); + if (dst_n + suffix_n + 1 > out_size) { + return -1; + } + + aster_memcpy(out, dst_root, dst_n); + aster_memcpy(out + dst_n, path + src_n, suffix_n); + out[dst_n + suffix_n] = '\0'; + return 0; +} + +static void fs_collect_cb(const char *name, u8 is_dir, u16 size) { + usize n; + (void)size; + + if (g_fs_tmp_count >= FS_TMP_MAX) { + return; + } + + n = aster_strlen(name); + if (n >= ASTERFS_NAME_LEN) { + n = ASTERFS_NAME_LEN - 1; + } + + aster_memcpy(g_fs_tmp_paths[g_fs_tmp_count], name, n); + g_fs_tmp_paths[g_fs_tmp_count][n] = '\0'; + g_fs_tmp_types[g_fs_tmp_count] = is_dir; + ++g_fs_tmp_count; +} + +static void fs_collect_all(void) { + g_fs_tmp_count = 0; + asterfs_list(fs_collect_cb); +} + +static int fs_dir_has_children(const char *path) { + int i; + + fs_collect_all(); + for (i = 0; i < g_fs_tmp_count; ++i) { + if (path_is_self_or_child(path, g_fs_tmp_paths[i]) && aster_strcmp(path, g_fs_tmp_paths[i]) != 0) { + return 1; + } + } + + return 0; +} + +static int fs_copy_file_abs(const char *src, const char *dst) { + u8 buf[ASTERFS_DATA_LEN]; + int n; + + if (asterfs_get_type(src) != 0) { + return -1; + } + + n = asterfs_read_file(src, buf, ASTERFS_DATA_LEN); + if (n < 0) { + return -1; + } + + if (asterfs_write_file(dst, buf, (u16)n) < 0) { + return -1; + } + + return 0; +} + +static int fs_copy_dir_abs(const char *src, const char *dst, int recursive) { + int i; + char mapped[ASTERFS_NAME_LEN]; + + if (asterfs_get_type(src) != 1) { + return -1; + } + + if (asterfs_get_type(dst) >= 0) { + return -1; + } + + if (!recursive && fs_dir_has_children(src)) { + return -2; + } + + if (asterfs_create_dir(dst) != 0) { + return -1; + } + + if (!recursive) { + return 0; + } + + fs_collect_all(); + + for (i = 0; i < g_fs_tmp_count; ++i) { + if (!g_fs_tmp_types[i]) { + continue; + } + if (aster_strcmp(g_fs_tmp_paths[i], src) == 0) { + continue; + } + if (!path_is_self_or_child(src, g_fs_tmp_paths[i])) { + continue; + } + + if (map_path_prefix(src, dst, g_fs_tmp_paths[i], mapped, sizeof(mapped)) != 0) { + return -1; + } + + if (asterfs_create_dir(mapped) != 0) { + return -1; + } + } + + for (i = 0; i < g_fs_tmp_count; ++i) { + if (g_fs_tmp_types[i]) { + continue; + } + if (!path_is_self_or_child(src, g_fs_tmp_paths[i])) { + continue; + } + + if (map_path_prefix(src, dst, g_fs_tmp_paths[i], mapped, sizeof(mapped)) != 0) { + return -1; + } + + if (fs_copy_file_abs(g_fs_tmp_paths[i], mapped) != 0) { + return -1; + } + } + + return 0; +} + +static int fs_remove_tree_abs(const char *root) { + int i; + int j; + + fs_collect_all(); + + for (i = 0; i < g_fs_tmp_count; ++i) { + if (g_fs_tmp_types[i]) { + continue; + } + if (!path_is_self_or_child(root, g_fs_tmp_paths[i])) { + continue; + } + if (asterfs_remove_file(g_fs_tmp_paths[i]) != 0) { + return -1; + } + } + + for (i = 0; i < g_fs_tmp_count; ++i) { + int best = -1; + usize best_len = 0; + + for (j = 0; j < g_fs_tmp_count; ++j) { + usize len; + if (!g_fs_tmp_types[j]) { + continue; + } + if (g_fs_tmp_paths[j][0] == '\0') { + continue; + } + if (!path_is_self_or_child(root, g_fs_tmp_paths[j])) { + continue; + } + + len = aster_strlen(g_fs_tmp_paths[j]); + if (best == -1 || len > best_len) { + best = j; + best_len = len; + } + } + + if (best == -1) { + break; + } + + if (asterfs_remove_dir(g_fs_tmp_paths[best]) != 0) { + return -1; + } + + g_fs_tmp_paths[best][0] = '\0'; + } + + return 0; +} + +static void editor_redraw(const char *path, const char *buf, usize len, usize pos) { + usize i; + usize line = 1; + usize cursor_row = 0; + usize cursor_col = 0; + int cursor_set = 0; + + for (i = 0; i < pos && i < len; ++i) { + if (buf[i] == '\n') { + ++line; + } + } + + display_set_color(0x0F, 0x00); + display_clear(); + display_set_color(0x0F, 0x01); + printk("EDITOR | file: %s | line: %u", path, (unsigned int)line); + display_set_color(0x0F, 0x00); + aster_print("\n\n"); + + for (i = 0; i < len; ++i) { + if (!cursor_set && i == pos) { + display_get_cursor(&cursor_row, &cursor_col); + cursor_set = 1; + } + display_putc(buf[i]); + } + + if (!cursor_set) { + display_get_cursor(&cursor_row, &cursor_col); + } + + aster_print("\n\n"); + display_set_color(0x0F, 0x00); + render_shell_statusbar(); + display_set_cursor(cursor_row, cursor_col); +} + +static void shell_edit_file(const char *path) { + char buf[ASTERFS_DATA_LEN + 1]; + int n = asterfs_read_file(path, (u8 *)buf, ASTERFS_DATA_LEN); + usize len; + usize pos; + unsigned long last_anim = timer_ticks(); + unsigned long idle_spin = 0; + unsigned long step_ticks = g_timer_hz / 5UL; + char title[120]; + usize tp = 0; + const char *prefix = "Upravovani souboru "; + const char *base = path_basename(path); + usize bi = 0; + + while (prefix[tp] && tp + 1 < sizeof(title)) { + title[tp] = prefix[tp]; + ++tp; + } + while (base[bi] && tp + 1 < sizeof(title)) { + title[tp++] = base[bi++]; + } + title[tp] = '\0'; + + if (step_ticks == 0) { + step_ticks = 1; + } + + if (n < 0) { + if (asterfs_create_file(path) != 0) { + aster_print("Editor: nelze vytvorit soubor\n"); + return; + } + n = 0; + } + + len = (usize)n; + buf[len] = '\0'; + pos = len; + g_status_marquee_only = 1; + status_set_left_hint("Ctrl+S = ulozit | ESC = uzavrit"); + status_set_marquee(title); + editor_redraw(path, buf, len, pos); + + for (;;) { + int key; + unsigned long now = timer_ticks(); + unsigned long delta; + unsigned long steps; + key = keyboard_try_read_key(); + + if (now > last_anim) { + delta = now - last_anim; + if (delta >= step_ticks) { + steps = delta / step_ticks; + g_status_marquee_offset += (usize)steps; + last_anim += steps * step_ticks; + render_shell_statusbar(); + } + } + + ++idle_spin; + if (idle_spin >= 230000UL) { + idle_spin = 0; + ++g_status_marquee_offset; + render_shell_statusbar(); + } + + if (key == -1) { + __asm__ volatile ("pause"); + continue; + } + + idle_spin = 0; + + if (key == 27) { + g_status_marquee_only = 0; + status_clear_left_hint(); + status_clear_marquee(); + display_clear(); + return; + } + + if (key == 19) { + (void)asterfs_write_file(path, (const u8 *)buf, (u16)len); + display_set_color(0x0A, 0x00); + aster_print("Saved\n"); + display_set_color(0x0F, 0x00); + editor_redraw(path, buf, len, pos); + continue; + } + + if (key == '\b') { + if (len > 0 && pos > 0) { + usize j; + for (j = pos - 1; j + 1 < len; ++j) { + buf[j] = buf[j + 1]; + } + --len; + --pos; + buf[len] = '\0'; + } + editor_redraw(path, buf, len, pos); + continue; + } + + if (key == ASTER_KEY_LEFT) { + if (pos > 0) { + --pos; + } + editor_redraw(path, buf, len, pos); + continue; + } + + if (key == ASTER_KEY_RIGHT) { + if (pos < len) { + ++pos; + } + editor_redraw(path, buf, len, pos); + continue; + } + + if (key == ASTER_KEY_UP) { + usize line_start = pos; + usize col; + + while (line_start > 0 && buf[line_start - 1] != '\n') { + --line_start; + } + + col = pos - line_start; + if (line_start > 0) { + usize prev_end = line_start - 1; + usize prev_start = prev_end; + usize prev_len; + + while (prev_start > 0 && buf[prev_start - 1] != '\n') { + --prev_start; + } + + prev_len = prev_end - prev_start; + pos = prev_start + (col < prev_len ? col : prev_len); + } + + editor_redraw(path, buf, len, pos); + continue; + } + + if (key == ASTER_KEY_DOWN) { + usize line_start = pos; + usize col; + usize line_end; + + while (line_start > 0 && buf[line_start - 1] != '\n') { + --line_start; + } + + col = pos - line_start; + line_end = pos; + while (line_end < len && buf[line_end] != '\n') { + ++line_end; + } + + if (line_end < len) { + usize next_start = line_end + 1; + usize next_end = next_start; + usize next_len; + + while (next_end < len && buf[next_end] != '\n') { + ++next_end; + } + + next_len = next_end - next_start; + pos = next_start + (col < next_len ? col : next_len); + } + + editor_redraw(path, buf, len, pos); + continue; + } + + if (key == '\n' || (key >= 32 && key <= 126)) { + if (len < ASTERFS_DATA_LEN) { + usize j; + for (j = len; j > pos; --j) { + buf[j] = buf[j - 1]; + } + buf[pos] = (char)key; + ++len; + buf[len] = '\0'; + ++pos; + editor_redraw(path, buf, len, pos); + } + } + } +} + +static void fs_list_cb(const char *name, u8 is_dir, u16 size) { + printk("%s %s %u\n", is_dir ? "DIR " : "FILE", name, (unsigned int)size); +} + +static void show_color_test(void) { + aster_print("\n"); + + display_set_color(0x00, 0x04); + aster_print(" "); + display_set_color(0x00, 0x02); + aster_print(" "); + display_set_color(0x00, 0x01); + aster_print(" "); + display_set_color(0x00, 0x0E); + aster_print(" "); + + display_set_color(0x0F, 0x00); + aster_print("\n\n"); +} + +static void run_c_like_script(const char *path) { + char src[ASTERFS_DATA_LEN + 1]; + int n = asterfs_read_file(path, (u8 *)src, ASTERFS_DATA_LEN); + unsigned long t0 = timer_ticks(); + int i; + int printed = 0; + + if (n < 0) { + print_error("Script nenalezen"); + print_exec_profile((const void *)run_c_like_script, t0); + return; + } + + src[n] = '\0'; + + for (i = 0; src[i] != '\0'; ++i) { + if (str_starts_with(&src[i], "printf(\"")) { + i += 8; + while (src[i] != '\0') { + char c = src[i++]; + + if (c == '\\') { + char esc = src[i++]; + if (esc == 'n') display_putc('\n'); + else if (esc == 't') display_putc('\t'); + else if (esc == '"') display_putc('"'); + else if (esc == '\\') display_putc('\\'); + else display_putc(esc); + continue; + } + + if (c == '"' && src[i] == ')') { + if (src[i + 1] == ';') { + ++i; + } + break; + } + + display_putc(c); + printed = 1; + } + } + } + + if (printed) { + display_putc('\n'); + } else { + print_error("Nepodporovany C kod. Podpora: printf(\"text\");"); + } + + print_exec_profile((const void *)run_c_like_script, t0); +} + +static void show_calc_ui(long a, long b, char op, long r) { + display_clear(); + display_set_color(0x0F, 0x01); + aster_print("+--------------------------------------+\n"); + aster_print("| ASTER CALCULATOR |\n"); + aster_print("+--------------------------------------+\n"); + display_set_color(0x0F, 0x00); + printk("\n %d %c %d = %d\n", (int)a, op, (int)b, (int)r); + aster_print("\nStiskni libovolnou klavesu pro navrat...\n"); + (void)keyboard_read_key(); + display_clear(); +} + +static void cmd_reboot(void) { + aster_print("Reboot...\n"); + __asm__ volatile ("cli"); + + if (kbc_wait_input_clear()) { + outb(0x64, 0xFE); + } + + outb(0xCF9, 0x02); + outb(0xCF9, 0x06); + + for (;;) { + __asm__ volatile ("hlt"); + } +} + +static void cmd_shutdown(void) { + aster_print("Shutdown...\n"); + __asm__ volatile ("cli"); + + outw(0x604, 0x2000); + outw(0xB004, 0x2000); + outw(0x4004, 0x3400); + outw(0x0604, 0x2000); + + for (;;) { + __asm__ volatile ("hlt"); + } +} + +static void print_process_line(const process_t *p) { + const char *state = "UNK"; + + if (p->state == PROCESS_READY) state = "READY"; + if (p->state == PROCESS_RUNNING) state = "RUN"; + if (p->state == PROCESS_BLOCKED) state = "BLOCK"; + if (p->state == PROCESS_EXITED) state = "EXIT"; + + printk("pid=%d state=%s prio=%d name=%s\n", (int)p->pid, state, (int)p->priority, p->name ? p->name : "-"); +} + +static void shell_processes(void) { + process_t *table = process_table(); + usize count = process_count(); + usize i; + + if (count == 0) { + aster_print("Zadne procesy\n"); + return; + } + + for (i = 0; i < count; ++i) { + print_process_line(&table[i]); + } +} + +static void demo_task_a(void) { + printk("[taskA] start\n"); +} + +static void demo_task_b(void) { + printk("[taskB] start\n"); +} + +static void shell_loop(void) { + char line[128]; + char full[ASTERFS_NAME_LEN]; + char aliased_cmd[32]; + char *cursor; + char *cmd; + const char *exec_cmd; + char *arg1; + char *arg2; + + for (;;) { + render_shell_statusbar(); + printk("[%s] A:%s> ", g_current_user[0] ? g_current_user : "guest", g_cwd); + keyboard_readline(line, sizeof(line)); + trim_inplace(line); + + cursor = line; + cmd = next_token(&cursor); + if (!cmd) { + continue; + } + + exec_cmd = cmd; + if (alias_lookup(cmd, aliased_cmd, sizeof(aliased_cmd))) { + exec_cmd = aliased_cmd; + } + + if (aster_strcmp(exec_cmd, "help") == 0) { + int ok = 0; + unsigned int page = 1; + arg1 = next_token(&cursor); + + if (arg1) { + page = (unsigned int)parse_u32(arg1, &ok); + if (!ok || page == 0) { + print_error("Pouziti: help [1|2|3]"); + } else { + shell_help_page(page); + } + } else { + shell_help_page(1); + } + } else if (aster_strcmp(exec_cmd, "helpall") == 0) { + shell_help_all(); + } else if (aster_strcmp(exec_cmd, "info") == 0) { + printk("AsterOS Kernel | Autor: Pavel Kalas | Rok: 2026\n"); + printk("tick=%u\n", (unsigned int)timer_ticks()); + } else if (aster_strcmp(exec_cmd, "memory") == 0) { + printk("total pages=%u free pages=%u\n", (unsigned int)memory_total_pages(), (unsigned int)memory_free_pages()); + } else if (aster_strcmp(exec_cmd, "process") == 0) { + shell_processes(); + } else if (aster_strcmp(exec_cmd, "clear") == 0) { + display_clear(); + } else if (aster_strcmp(exec_cmd, "ticks") == 0) { + printk("ticks=%u\n", (unsigned int)timer_ticks()); + } else if (aster_strcmp(exec_cmd, "echo") == 0) { + while (*cursor && is_space(*cursor)) { + ++cursor; + } + printk("%s\n", cursor); + } else if (aster_strcmp(exec_cmd, "ls") == 0) { + asterfs_list_dir(g_cwd, fs_list_cb); + } else if (aster_strcmp(exec_cmd, "cd") == 0) { + arg1 = next_token(&cursor); + if (!arg1) { + print_error("Pouziti: cd "); + } else { + resolve_path(arg1, full, sizeof(full)); + if (asterfs_get_type(full) == 1) { + aster_memset(g_cwd, 0, sizeof(g_cwd)); + aster_memcpy(g_cwd, full, aster_strlen(full)); + } else { + print_error("Slozka neexistuje"); + } + } + } else if (aster_strcmp(exec_cmd, "makdir") == 0) { + arg1 = next_token(&cursor); + if (!arg1) { + print_error("Pouziti: makdir "); + } else { + resolve_path(arg1, full, sizeof(full)); + if (asterfs_create_dir(full) == 0) { + aster_print("OK\n"); + } else { + print_error("Chyba create dir"); + } + } + } else if (aster_strcmp(exec_cmd, "remdir") == 0) { + arg1 = next_token(&cursor); + if (!arg1) { + print_error("Pouziti: remdir "); + } else { + resolve_path(arg1, full, sizeof(full)); + if (aster_strcmp(full, "/") == 0) { + print_error("Nelze smazat root slozku /"); + } else if (asterfs_get_type(full) != 1) { + print_error("Slozka neexistuje"); + } else if (asterfs_remove_dir(full) == 0 || fs_remove_tree_abs(full) == 0) { + aster_print("OK\n"); + } else { + print_error("Chyba remove dir"); + } + } + } else if (aster_strcmp(exec_cmd, "copdir") == 0 || aster_strcmp(exec_cmd, "movdir") == 0) { + int rec = 0; + int is_move = (aster_strcmp(exec_cmd, "movdir") == 0); + char src[ASTERFS_NAME_LEN]; + char dst[ASTERFS_NAME_LEN]; + char *arg3; + int rc; + + arg1 = next_token(&cursor); + arg2 = next_token(&cursor); + arg3 = next_token(&cursor); + + if (!arg1 || !arg2) { + print_error("Pouziti: copdir|movdir [-rec]"); + continue; + } + + if (arg3) { + if (aster_strcmp(arg3, "-rec") == 0) { + rec = 1; + } else { + print_error("Neznamy prepinac. Pouzij -rec"); + continue; + } + } + + resolve_path(arg1, src, sizeof(src)); + resolve_path(arg2, dst, sizeof(dst)); + + rc = fs_copy_dir_abs(src, dst, rec); + if (rc == -2) { + print_error("Slozka neni prazdna. Pouzij -rec"); + continue; + } + if (rc != 0) { + print_error("Chyba kopie slozky"); + continue; + } + + if (is_move) { + if (!rec && fs_dir_has_children(src)) { + print_error("Move dir: pouzij -rec pro nepradznou slozku"); + continue; + } + + if (rec) { + if (fs_remove_tree_abs(src) != 0) { + print_error("Chyba remove puvodni slozky"); + continue; + } + } else if (asterfs_remove_dir(src) != 0) { + print_error("Chyba remove puvodni slozky"); + continue; + } + } + + aster_print("OK\n"); + } else if (aster_strcmp(exec_cmd, "makfile") == 0) { + arg1 = next_token(&cursor); + if (!arg1) { + print_error("Pouziti: makfile "); + } else { + resolve_path(arg1, full, sizeof(full)); + if (asterfs_create_file(full) == 0) { + aster_print("OK\n"); + } else { + print_error("Chyba create file"); + } + } + } else if (aster_strcmp(exec_cmd, "remfile") == 0) { + arg1 = next_token(&cursor); + if (!arg1) { + print_error("Pouziti: remfile "); + } else { + resolve_path(arg1, full, sizeof(full)); + if (asterfs_remove_file(full) == 0) { + aster_print("OK\n"); + } else { + print_error("Chyba remove file"); + } + } + } else if (aster_strcmp(exec_cmd, "copfile") == 0 || aster_strcmp(exec_cmd, "movfile") == 0) { + int is_move = (aster_strcmp(exec_cmd, "movfile") == 0); + char src[ASTERFS_NAME_LEN]; + char dst[ASTERFS_NAME_LEN]; + + arg1 = next_token(&cursor); + arg2 = next_token(&cursor); + if (!arg1 || !arg2) { + print_error("Pouziti: copfile|movfile "); + continue; + } + + resolve_path(arg1, src, sizeof(src)); + resolve_path(arg2, dst, sizeof(dst)); + + if (asterfs_get_type(dst) == 1) { + const char *base = path_basename(src); + usize dlen = aster_strlen(dst); + usize blen = aster_strlen(base); + if (dlen + 1 + blen >= sizeof(dst)) { + print_error("Cilova cesta je moc dlouha"); + continue; + } + dst[dlen] = '/'; + aster_memcpy(dst + dlen + 1, base, blen); + dst[dlen + 1 + blen] = '\0'; + } + + if (fs_copy_file_abs(src, dst) != 0) { + print_error("Chyba kopie souboru"); + continue; + } + + if (is_move && asterfs_remove_file(src) != 0) { + print_error("Chyba remove puvodniho souboru"); + continue; + } + + aster_print("OK\n"); + } else if (aster_strcmp(exec_cmd, "cat") == 0) { + u8 buf[ASTERFS_DATA_LEN + 1]; + int n; + unsigned long t0 = timer_ticks(); + arg1 = next_token(&cursor); + if (!arg1) { + print_error("Pouziti: read "); + } else { + resolve_path(arg1, full, sizeof(full)); + n = asterfs_read_file(full, buf, ASTERFS_DATA_LEN); + if (n < 0) { + print_error("Soubor nenalezen"); + } else { + buf[n] = '\0'; + printk("%s\n", (char *)buf); + } + } + print_exec_profile((const void *)asterfs_read_file, t0); + } else if (aster_strcmp(exec_cmd, "write") == 0) { + int w; + unsigned long t0 = timer_ticks(); + arg1 = next_token(&cursor); + while (*cursor && is_space(*cursor)) { + ++cursor; + } + arg2 = cursor; + + if (!arg1 || !arg2 || *arg2 == '\0') { + print_error("Pouziti: write "); + } else { + resolve_path(arg1, full, sizeof(full)); + w = asterfs_write_file(full, (const u8 *)arg2, (u16)aster_strlen(arg2)); + if (w < 0) { + print_error("Chyba zapis"); + } else { + printk("Zapsano %d B\n", w); + } + } + print_exec_profile((const void *)asterfs_write_file, t0); + } else if (aster_strcmp(exec_cmd, "setup") == 0) { + if (system_is_installed()) { + print_error("Neznamy prikaz. Zadej help."); + } else { + cmd_setup_install(); + } + } else if (aster_strcmp(exec_cmd, "asrun") == 0) { + arg1 = next_token(&cursor); + if (!arg1) { + print_error("Pouziti: asrun "); + } else { + resolve_path(arg1, full, sizeof(full)); + run_aster_script(full); + } + } else if (aster_strcmp(exec_cmd, "fm") == 0) { + run_file_manager(); + } else if (aster_strcmp(exec_cmd, "edit") == 0) { + arg1 = next_token(&cursor); + if (!arg1) { + aster_print("Pouziti: edit \n"); + } else { + resolve_path(arg1, full, sizeof(full)); + shell_edit_file(full); + } + } else if (aster_strcmp(exec_cmd, "calc") == 0) { + int oka; + int okb; + long a; + long b; + long r = 0; + char op; + char *arg3; + unsigned long t0 = timer_ticks(); + unsigned long calc_delta_ticks = 0; + + arg1 = next_token(&cursor); + arg2 = next_token(&cursor); + arg3 = next_token(&cursor); + + if (!arg1 || !arg2 || !arg3 || aster_strlen(arg2) != 1) { + print_error("Pouziti: calc "); + } else { + a = parse_i32(arg1, &oka); + b = parse_i32(arg3, &okb); + op = arg2[0]; + + if (!oka || !okb) { + print_error("Chyba: A/B musi byt cisla"); + } else { + if (op == '+') r = a + b; + else if (op == '-') r = a - b; + else if (op == '*') r = a * b; + else if (op == '/') { + if (b == 0) { + print_error("Chyba: deleni nulou"); + print_exec_profile((const void *)show_calc_ui, t0); + continue; + } + r = a / b; + } else { + print_error("Operator musi byt + - * /"); + print_exec_profile((const void *)show_calc_ui, t0); + continue; + } + + calc_delta_ticks = timer_ticks() - t0; + show_calc_ui(a, b, op, r); + } + } + + if (calc_delta_ticks != 0) { + print_exec_profile_ticks((const void *)show_calc_ui, calc_delta_ticks); + } else { + print_exec_profile((const void *)show_calc_ui, t0); + } + } else if (aster_strcmp(exec_cmd, "alloc") == 0) { + int ok; + unsigned long n; + void *p; + arg1 = next_token(&cursor); + n = parse_u32(arg1, &ok); + if (!ok) { + print_error("Pouziti: alloc "); + } else { + p = kmalloc((usize)n); + printk("alloc -> %p\n", p); + } + } else if (aster_strcmp(exec_cmd, "useradd") == 0) { + char pass[AUTH_PASS_LEN]; + arg1 = next_token(&cursor); + arg2 = next_token(&cursor); + if (!arg1) { + print_error("Pouziti: useradd [pass]"); + continue; + } + + pass[0] = '\0'; + if (arg2) { + usize plen = aster_strlen(arg2); + if (plen >= sizeof(pass)) { + print_error("Heslo je moc dlouhe"); + continue; + } + aster_memcpy(pass, arg2, plen); + pass[plen] = '\0'; + } + + if (auth_add_user(arg1, pass) != 0 || auth_save_users() != 0) { + print_error("Chyba pri vytvareni uzivatele"); + } else { + aster_print("OK\n"); + } + } else if (aster_strcmp(exec_cmd, "passwdch") == 0) { + char new_pass[AUTH_PASS_LEN]; + const char *target; + + arg1 = next_token(&cursor); + target = arg1 ? arg1 : g_current_user; + + if (!target || target[0] == '\0' || auth_find_user(target) < 0) { + print_error("Uzivatel neexistuje"); + continue; + } + + aster_print("Nove heslo (prazdne = bez hesla): "); + auth_readline_secret(new_pass, sizeof(new_pass)); + + if (auth_set_pass(target, new_pass) != 0 || auth_save_users() != 0) { + print_error("Chyba zmeny hesla"); + } else { + aster_print("OK\n"); + } + } else if (aster_strcmp(exec_cmd, "exit") == 0) { + aster_memset(g_current_user, 0, sizeof(g_current_user)); + return; + } else if (exec_cmd[0] == '.' && exec_cmd[1] == '/' && exec_cmd[2] != '\0') { + resolve_path(exec_cmd + 2, full, sizeof(full)); + run_c_like_script(full); + } else if (aster_strcmp(exec_cmd, "reboot") == 0) { + cmd_reboot(); + } else if (aster_strcmp(exec_cmd, "shutdown") == 0) { + cmd_shutdown(); + } else { + print_error("Neznamy prikaz. Zadej help."); + } + } +} + +void kmain(void) { + display_init(); + boot_splash_begin(8); + + boot_step_begin("CPU setup"); + boot_step_skip("CPU setup"); + + boot_step_begin("Memory manager"); + memory_init(); + boot_step_ok("Memory manager"); + + boot_step_begin("Process subsystem"); + process_init(); + scheduler_init(); + boot_step_ok("Process subsystem"); + + boot_step_begin("Syscall interface"); + syscall_init(); + boot_step_ok("Syscall interface"); + + boot_step_begin("Keyboard driver"); + keyboard_init(); + boot_step_ok("Keyboard driver"); + + boot_step_begin("Timer driver"); + timer_init((unsigned int)g_timer_hz); + boot_step_ok("Timer driver"); + + boot_step_begin("Storage + AsterFS"); + storage_init(); + boot_step_ok("Storage + AsterFS"); + + (void)aster_process_create(demo_task_a, "initA", 1); + (void)aster_process_create(demo_task_b, "initB", 1); + scheduler_run_once(); + + boot_step_begin("Interrupts"); + boot_step_skip("Interrupts"); + + boot_splash_end(); + display_clear(); + show_welcome_banner(); + show_color_test(); + aster_print("Pro zobrazeni vsech prikazu zadej 'help'\n"); + + for (;;) { + auth_login_screen(); + ensure_aliases_file(); + display_clear(); + if (system_is_installed()) { + show_aster_banner("Shell"); + } else { + show_aster_banner("Live shell"); + aster_print("Live mode: bez hesla\n"); + aster_print("Pro instalaci na disk spust: setup\n"); + } + aster_print("Pro zobrazeni prikazu pouzij 'help'\n"); + shell_loop(); + } +} diff --git a/kernel/memory.c b/kernel/memory.c new file mode 100644 index 0000000..f04b534 --- /dev/null +++ b/kernel/memory.c @@ -0,0 +1,106 @@ +/* + * AsterOS Kernel + * Autor: Pavel Kalaš + * Rok: 2026 + * + */ + +/* + * Tento modul realizuje dve vrstvy spravy pameti jadra. + * Prvni cast je bitmap allocator fyzickych stranek pro page_alloc/page_free, + * druha cast je jednoducha linearni heap alokace pro kernelove objekty. + */ + +#include "memory.h" +#include "string.h" + +#define PMM_MAX_MEMORY (64ULL * 1024ULL * 1024ULL) +#define PMM_MAX_PAGES (PMM_MAX_MEMORY / PAGE_SIZE) +#define PMM_BITMAP_SIZE (PMM_MAX_PAGES / 8) + +static u8 page_bitmap[PMM_BITMAP_SIZE]; +static usize free_pages = PMM_MAX_PAGES; + +#define KHEAP_SIZE (1024 * 1024) +static u8 kernel_heap[KHEAP_SIZE]; +static usize heap_offset = 0; + +static void bitmap_set(usize idx) { + page_bitmap[idx / 8] |= (u8)(1u << (idx % 8)); +} + +static void bitmap_clear(usize idx) { + page_bitmap[idx / 8] &= (u8)~(1u << (idx % 8)); +} + +static int bitmap_test(usize idx) { + return (page_bitmap[idx / 8] & (u8)(1u << (idx % 8))) != 0; +} + +void memory_init(void) { + aster_memset(page_bitmap, 0, PMM_BITMAP_SIZE); + free_pages = PMM_MAX_PAGES; + heap_offset = 0; + + for (usize i = 0; i < 256; ++i) { + bitmap_set(i); + --free_pages; + } +} + +void *page_alloc(void) { + usize i; + + for (i = 0; i < PMM_MAX_PAGES; ++i) { + if (!bitmap_test(i)) { + bitmap_set(i); + --free_pages; + return (void *)(i * PAGE_SIZE); + } + } + + return 0; +} + +void page_free(void *page) { + usize idx; + + if (!page) { + return; + } + + idx = ((usize)page) / PAGE_SIZE; + if (idx < PMM_MAX_PAGES && bitmap_test(idx)) { + bitmap_clear(idx); + ++free_pages; + } +} + +void *kmalloc(usize size) { + usize aligned; + + if (size == 0) { + return 0; + } + + aligned = (size + 15) & ~((usize)15); + if (heap_offset + aligned > KHEAP_SIZE) { + return 0; + } + + void *ptr = &kernel_heap[heap_offset]; + heap_offset += aligned; + return ptr; +} + +void kfree(void *ptr) { + (void)ptr; +} + +usize memory_total_pages(void) { + return PMM_MAX_PAGES; +} + +usize memory_free_pages(void) { + return free_pages; +} diff --git a/kernel/panic.c b/kernel/panic.c new file mode 100644 index 0000000..6ce7a59 --- /dev/null +++ b/kernel/panic.c @@ -0,0 +1,23 @@ +/* + * AsterOS Kernel + * Autor: Pavel Kalaš + * Rok: 2026 + * + */ + +/* + * Tento soubor drzi nouzovou cestu pro neobnovitelne chyby jadra. + * Pri volani panic prepne vizualni styl vystupu, vypise duvod selhani + * a bezpecne zastavi CPU, aby se predeslo dalsimu poskozeni stavu. + */ + +#include "cpu.h" +#include "display.h" +#include "panic.h" +#include "printk.h" + +void panic(const char *reason) { + display_set_color(0x0F, 0x04); + printk("\n[KERNEL PANIC] %s\n", reason ? reason : "unknown"); + cpu_halt(); +} diff --git a/kernel/printk.c b/kernel/printk.c new file mode 100644 index 0000000..4c3d5a4 --- /dev/null +++ b/kernel/printk.c @@ -0,0 +1,106 @@ +/* + * AsterOS Kernel + * Autor: Pavel Kalaš + * Rok: 2026 + * + */ + +/* + * Tento modul implementuje tiskovou vrstvu kernelu nad VGA vystupem. + * Obsahuje podporu zakladnich formatovacich specifikatoru pro ladeni + * a jednotne API, ktere pouzivaji ostatni casti jadra pri logovani. + */ + +#include + +#include "display.h" +#include "printk.h" + +static void print_u64(unsigned long long value, unsigned int base) { + char buf[32]; + const char *digits = "0123456789abcdef"; + int i = 0; + + if (value == 0) { + display_putc('0'); + return; + } + + while (value > 0 && i < (int)sizeof(buf)) { + buf[i++] = digits[value % base]; + value /= base; + } + + while (i > 0) { + display_putc(buf[--i]); + } +} + +void aster_print(const char *text) { + display_write(text); +} + +void printk(const char *fmt, ...) { + va_list args; + + va_start(args, fmt); + + while (*fmt) { + if (*fmt != '%') { + display_putc(*fmt++); + continue; + } + + ++fmt; + switch (*fmt) { + case '%': + display_putc('%'); + break; + case 'c': { + int c = va_arg(args, int); + display_putc((char)c); + break; + } + case 's': { + const char *s = va_arg(args, const char *); + if (!s) { + s = "(null)"; + } + display_write(s); + break; + } + case 'd': { + long v = va_arg(args, int); + if (v < 0) { + display_putc('-'); + print_u64((unsigned long long)(-v), 10); + } else { + print_u64((unsigned long long)v, 10); + } + break; + } + case 'u': { + unsigned int v = va_arg(args, unsigned int); + print_u64(v, 10); + break; + } + case 'x': { + unsigned int v = va_arg(args, unsigned int); + print_u64(v, 16); + break; + } + case 'p': { + unsigned long long v = (unsigned long long)va_arg(args, void *); + display_write("0x"); + print_u64(v, 16); + break; + } + default: + display_putc('?'); + break; + } + ++fmt; + } + + va_end(args); +} diff --git a/kernel/process.c b/kernel/process.c new file mode 100644 index 0000000..331d890 --- /dev/null +++ b/kernel/process.c @@ -0,0 +1,91 @@ +/* + * AsterOS Kernel + * Autor: Pavel Kalaš + * Rok: 2026 + * + */ + +/* + * Tento soubor spravuje zivotni cyklus kernel procesu. + * Implementuje statickou procesovou tabulku, prirazovani PID, + * pripravu stacku a zakladni operace pro vytvoreni a ukonceni procesu. + */ + +#include "memory.h" +#include "process.h" +#include "string.h" + +#define PROCESS_STACK_SIZE (16 * 1024) + +static process_t g_processes[ASTER_MAX_PROCESSES]; +static usize g_process_count = 0; +static u32 g_next_pid = 1; +static process_t *g_current = 0; + +void process_init(void) { + usize i; + + for (i = 0; i < ASTER_MAX_PROCESSES; ++i) { + g_processes[i].pid = 0; + g_processes[i].state = PROCESS_UNUSED; + g_processes[i].priority = 0; + g_processes[i].stack_base = 0; + g_processes[i].stack_top = 0; + g_processes[i].name = 0; + g_processes[i].context.rip = 0; + } + + g_process_count = 0; + g_current = 0; +} + +process_t *process_create(const char *name, void (*entry)(void), u8 priority) { + process_t *p; + void *stack; + + if (g_process_count >= ASTER_MAX_PROCESSES || !entry) { + return 0; + } + + p = &g_processes[g_process_count]; + stack = kmalloc(PROCESS_STACK_SIZE); + if (!stack) { + return 0; + } + + p->pid = g_next_pid++; + p->state = PROCESS_READY; + p->priority = priority; + p->stack_base = (u8 *)stack; + p->stack_top = (u64)(p->stack_base + PROCESS_STACK_SIZE - 16); + p->name = name; + + aster_memset(&p->context, 0, sizeof(context_t)); + p->context.rip = (u64)entry; + p->context.rbp = p->stack_top; + + ++g_process_count; + return p; +} + +void process_exit(void) { + if (g_current) { + g_current->state = PROCESS_EXITED; + } +} + +process_t *process_current(void) { + return g_current; +} + +void process_set_current(process_t *p) { + g_current = p; +} + +process_t *process_table(void) { + return &g_processes[0]; +} + +usize process_count(void) { + return g_process_count; +} diff --git a/kernel/scheduler.c b/kernel/scheduler.c new file mode 100644 index 0000000..f93f8ef --- /dev/null +++ b/kernel/scheduler.c @@ -0,0 +1,81 @@ +/* + * AsterOS Kernel + * Autor: Pavel Kalaš + * Rok: 2026 + * + */ + +/* + * Tento modul implementuje jednoduchy planovac typu round-robin. + * Reaguje na tiky casovace, vybira dalsi bezici proces + * a ridi prepinani mezi stavy READY, RUNNING a EXITED. + */ + +#include "process.h" +#include "scheduler.h" +#include "timer.h" + +static usize g_current_index = 0; +static unsigned long g_quantum_ticks = 0; + +void scheduler_init(void) { + g_current_index = 0; + g_quantum_ticks = 0; +} + +process_t *scheduler_pick_next(void) { + process_t *table = process_table(); + usize count = process_count(); + usize i; + + if (count == 0) { + return 0; + } + + for (i = 0; i < count; ++i) { + usize idx = (g_current_index + i + 1) % count; + if (table[idx].state == PROCESS_READY || table[idx].state == PROCESS_RUNNING) { + g_current_index = idx; + return &table[idx]; + } + } + + return 0; +} + +void scheduler_run_once(void) { + process_t *next = scheduler_pick_next(); + + if (!next) { + return; + } + + process_set_current(next); + if (next->state == PROCESS_READY) { + void (*entry)(void) = (void (*)(void))next->context.rip; + next->state = PROCESS_RUNNING; + entry(); + if (next->state == PROCESS_RUNNING) { + next->state = PROCESS_EXITED; + } + } +} + +void scheduler_yield(void) { + process_t *current = process_current(); + + if (current && current->state == PROCESS_RUNNING) { + current->state = PROCESS_READY; + } + + scheduler_run_once(); +} + +void scheduler_tick(void) { + timer_tick_advance(); + ++g_quantum_ticks; + + if ((g_quantum_ticks % 5) == 0) { + scheduler_run_once(); + } +} diff --git a/kernel/string.c b/kernel/string.c new file mode 100644 index 0000000..2a867b1 --- /dev/null +++ b/kernel/string.c @@ -0,0 +1,65 @@ +/* + * AsterOS Kernel + * Autor: Pavel Kalaš + * Rok: 2026 + * + */ + +/* + * Tento soubor obsahuje vlastni implementace zakladnich utility funkci. + * Kernel je pouziva misto standardni knihovny pro strlen, strcmp, + * memcpy a memset, aby zustal freestanding a plne pod kontrolou jadra. + */ + +#include "string.h" + +usize aster_strlen(const char *s) { + usize n = 0; + while (s && s[n]) { + ++n; + } + return n; +} + +int aster_strcmp(const char *a, const char *b) { + while (*a && (*a == *b)) { + ++a; + ++b; + } + return (unsigned char)*a - (unsigned char)*b; +} + +int aster_strncmp(const char *a, const char *b, usize n) { + usize i; + + for (i = 0; i < n; ++i) { + if (a[i] != b[i] || a[i] == '\0' || b[i] == '\0') { + return (unsigned char)a[i] - (unsigned char)b[i]; + } + } + + return 0; +} + +void *aster_memcpy(void *dst, const void *src, usize n) { + usize i; + u8 *d = (u8 *)dst; + const u8 *s = (const u8 *)src; + + for (i = 0; i < n; ++i) { + d[i] = s[i]; + } + + return dst; +} + +void *aster_memset(void *dst, int v, usize n) { + usize i; + u8 *d = (u8 *)dst; + + for (i = 0; i < n; ++i) { + d[i] = (u8)v; + } + + return dst; +} diff --git a/kernel/syscall.c b/kernel/syscall.c new file mode 100644 index 0000000..07ec272 --- /dev/null +++ b/kernel/syscall.c @@ -0,0 +1,58 @@ +/* + * AsterOS Kernel + * Autor: Pavel Kalaš + * Rok: 2026 + * + */ + +/* + * Tento modul je vstupni brana pro systemova volani. + * Obsahuje dispatcher, ktery podle ID vola konkretni kernelovou sluzbu, + * a implementuje zakladni syscall operace pro zapis, alokaci a procesy. + */ + +#include "memory.h" +#include "printk.h" +#include "process.h" +#include "syscall.h" + +void syscall_init(void) { +} + +u64 syscall_dispatch(u64 id, u64 a1, u64 a2, u64 a3, u64 a4) { + (void)a3; + (void)a4; + + switch (id) { + case SYSCALL_WRITE: + return aster_write((const char *)a1); + case SYSCALL_ALLOC: + return (u64)aster_alloc((usize)a1); + case SYSCALL_PROCESS_CREATE: + return (u64)aster_process_create((void (*)(void))a1, (const char *)a2, (u8)a3); + default: + return (u64)-1; + } +} + +u64 aster_write(const char *text) { + if (!text) { + return (u64)-1; + } + + printk("%s", text); + return 0; +} + +void *aster_alloc(usize size) { + return kmalloc(size); +} + +i64 aster_process_create(void (*entry)(void), const char *name, u8 priority) { + process_t *p = process_create(name, entry, priority); + if (!p) { + return -1; + } + + return p->pid; +} diff --git a/linker.ld b/linker.ld new file mode 100644 index 0000000..4a5f601 --- /dev/null +++ b/linker.ld @@ -0,0 +1,37 @@ +/* + * AsterOS Kernel + * Autor: Pavel Kalaš + * Rok: 2026 + * + * Vytvořeno jen tak z nudy a z chuti zkoušet nové věci. + */ + +OUTPUT_FORMAT(elf64-x86-64) +ENTRY(_start) + +SECTIONS +{ + . = 0x00100000; + + .text : + { + *(.text.boot) + *(.text*) + } + + .rodata : + { + *(.rodata*) + } + + .data : + { + *(.data*) + } + + .bss : + { + *(COMMON) + *(.bss*) + } +}