diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md index d7e81c2..5ce85ff 100644 --- a/.github/pull_request_template.md +++ b/.github/pull_request_template.md @@ -9,6 +9,13 @@ Describe the changes made in this PR. - [ ] Code has been tested - [ ] Existing tests pass +### Platform / Environment +What platform and environment were used for development and testing? + +Examples: +- Windows 11 / macOS / Linux +- MSYS2 / WSL2 / Debian + Notes: diff --git a/Makefile b/Makefile index 4e05fc8..4ce93a7 100644 --- a/Makefile +++ b/Makefile @@ -36,43 +36,21 @@ USERLAND_METADATA_ICONS = $(shell { \ } | tr ';' '\n' | sed 's@.*/@@' | sed '/^[[:space:]]*$$/d' | sort -u) COLLOID_ICONS = $(sort $(DOCK_COLLOID_ICONS) $(USERLAND_COLLOID_ICONS) $(USERLAND_METADATA_ICONS) xterm.png) -C_SOURCES = $(wildcard $(SRC_DIR)/core/*.c) \ - $(wildcard $(SRC_DIR)/sys/*.c) \ - $(wildcard $(SRC_DIR)/mem/*.c) \ - $(wildcard $(SRC_DIR)/dev/*.c) \ - $(wildcard $(SRC_DIR)/drivers/*.c) \ - $(wildcard $(SRC_DIR)/input/*.c) \ - $(wildcard $(SRC_DIR)/net/*.c) \ - $(wildcard $(SRC_DIR)/net/nic/*.c) \ - $(wildcard $(SRC_DIR)/fs/*.c) \ - $(wildcard $(SRC_DIR)/wm/*.c) \ - $(wildcard $(SRC_DIR)/net/third_party/lwip/core/*.c) \ - $(wildcard $(SRC_DIR)/net/third_party/lwip/core/ipv4/*.c) \ - $(SRC_DIR)/net/third_party/lwip/netif/ethernet.c \ - $(SRC_DIR)/net/third_party/lwip/netif/bridgeif.c +C_SOURCES := $(shell find $(SRC_DIR) -type f -name '*.c' \ + ! -path '$(SRC_DIR)/userland/*' \ + ! -path '*/third_party/lwip/netif/slipif.c') +ASM_SOURCES := $(shell find $(SRC_DIR) -type f -name '*.asm' ! -path '$(SRC_DIR)/userland/*') -ASM_SOURCES = $(wildcard $(SRC_DIR)/arch/*.asm) -OBJ_FILES = $(patsubst $(SRC_DIR)/core/%.c, $(BUILD_DIR)/%.o, $(wildcard $(SRC_DIR)/core/*.c)) \ - $(patsubst $(SRC_DIR)/sys/%.c, $(BUILD_DIR)/%.o, $(wildcard $(SRC_DIR)/sys/*.c)) \ - $(patsubst $(SRC_DIR)/mem/%.c, $(BUILD_DIR)/%.o, $(wildcard $(SRC_DIR)/mem/*.c)) \ - $(patsubst $(SRC_DIR)/dev/%.c, $(BUILD_DIR)/%.o, $(wildcard $(SRC_DIR)/dev/*.c)) \ - $(patsubst $(SRC_DIR)/drivers/%.c, $(BUILD_DIR)/%.o, $(wildcard $(SRC_DIR)/drivers/*.c)) \ - $(patsubst $(SRC_DIR)/input/%.c, $(BUILD_DIR)/%.o, $(wildcard $(SRC_DIR)/input/*.c)) \ - $(patsubst $(SRC_DIR)/net/%.c, $(BUILD_DIR)/%.o, $(wildcard $(SRC_DIR)/net/*.c)) \ - $(patsubst $(SRC_DIR)/net/nic/%.c, $(BUILD_DIR)/%.o, $(wildcard $(SRC_DIR)/net/nic/*.c)) \ - $(patsubst $(SRC_DIR)/fs/%.c, $(BUILD_DIR)/%.o, $(wildcard $(SRC_DIR)/fs/*.c)) \ - $(patsubst $(SRC_DIR)/wm/%.c, $(BUILD_DIR)/%.o, $(wildcard $(SRC_DIR)/wm/*.c)) \ - $(patsubst $(SRC_DIR)/net/third_party/lwip/%.c, $(BUILD_DIR)/lwip/%.o, $(filter $(SRC_DIR)/net/third_party/lwip/%.c, $(C_SOURCES))) \ - $(patsubst $(SRC_DIR)/arch/%.asm, $(BUILD_DIR)/%.o, $(ASM_SOURCES)) +OBJ_FILES := $(patsubst $(SRC_DIR)/%.c, $(BUILD_DIR)/%.o, $(C_SOURCES)) \ + $(patsubst $(SRC_DIR)/%.asm, $(BUILD_DIR)/%.o, $(ASM_SOURCES)) + +INCLUDE_DIRS := $(shell find $(SRC_DIR) -type d ! -path '$(SRC_DIR)/userland*') +INCLUDES := $(patsubst %, -I%, $(INCLUDE_DIRS)) CFLAGS = -g -O2 -pipe -Wall -Wextra -std=gnu11 -ffreestanding \ -fno-stack-protector -fno-stack-check -fno-lto -fPIE \ -m64 -march=x86-64 -msse -msse2 -mstackrealign -mno-red-zone \ - -I$(SRC_DIR) -I$(SRC_DIR)/net/third_party/lwip -I$(SRC_DIR)/core \ - -I$(SRC_DIR)/sys -I$(SRC_DIR)/mem -I$(SRC_DIR)/dev \ - -I$(SRC_DIR)/drivers \ - -I$(SRC_DIR)/net -I$(SRC_DIR)/net/nic -I$(SRC_DIR)/fs \ - -I$(SRC_DIR)/wm -I$(SRC_DIR)/input + $(INCLUDES) LDFLAGS = -m elf_x86_64 -nostdlib -static -pie --no-dynamic-linker \ -z text -z max-page-size=0x1000 -T linker.ld @@ -94,118 +72,52 @@ all: $(BUILD_DIR): $(call PRINT_STEP,CREATING BUILD DIRECTORY) mkdir -p $(BUILD_DIR) - mkdir -p $(BUILD_DIR) limine-setup: $(call PRINT_STEP,SETTING UP LIMINE) @if [ ! -f limine/limine-bios.sys ]; then \ - printf "$(YELLOW)[LIMINE] Limine binaries missing or invalid. Cloning v$(LIMINE_VERSION)-binary...$(RESET)"; \ + printf "$(YELLOW)[LIMINE] Limine binaries missing or invalid. Cloning v$(LIMINE_VERSION)-binary...$(RESET)\n"; \ rm -rf limine; \ git clone https://github.com/limine-bootloader/limine.git --branch=v$(LIMINE_VERSION)-binary --depth=1 limine; \ else \ - printf "$(YELLOW)[LIMINE] Existing Limine binaries found.$(RESET)"; \ + printf "$(YELLOW)[LIMINE] Existing Limine binaries found.$(RESET)\n"; \ fi @if [ ! -f $(SRC_DIR)/core/limine.h ]; then \ - printf "$(YELLOW)[LIMINE] Copying limine.h...$(RESET)"; \ + printf "$(YELLOW)[LIMINE] Copying limine.h...$(RESET)\n"; \ cp limine/limine.h $(SRC_DIR)/core/limine.h; \ else \ - printf "$(YELLOW)[LIMINE] limine.h already present.$(RESET)"; \ + printf "$(YELLOW)[LIMINE] limine.h already present.$(RESET)\n"; \ fi - @printf "$(YELLOW)[LIMINE] Building Limine host utility...$(RESET)" + @printf "$(YELLOW)[LIMINE] Building Limine host utility...$(RESET)\n" $(MAKE) -C limine - @printf "$(GREEN)[OK] Limine setup complete.$(RESET)" + @printf "$(GREEN)[OK] Limine setup complete.$(RESET)\n" $(BUILD_DIR)/%.o: $(SRC_DIR)/%.c | $(BUILD_DIR) limine-setup - @printf "$(YELLOW)[CC]$(RESET) $< -> $@" - mkdir -p $(dir $@) + @printf "$(YELLOW)[CC]$(RESET) $< -> $@\n" + @mkdir -p $(dir $@) $(CC) $(CFLAGS) -c $< -o $@ -$(BUILD_DIR)/%.o: $(SRC_DIR)/core/%.c | $(BUILD_DIR) limine-setup - @printf "$(YELLOW)[CC]$(RESET)[core] $< -> $@" - mkdir -p $(dir $@) - $(CC) $(CFLAGS) -c $< -o $@ - -$(BUILD_DIR)/%.o: $(SRC_DIR)/sys/%.c | $(BUILD_DIR) limine-setup - @printf "$(YELLOW)[CC]$(RESET)[sys] $< -> $@" - mkdir -p $(dir $@) - $(CC) $(CFLAGS) -c $< -o $@ - -$(BUILD_DIR)/%.o: $(SRC_DIR)/mem/%.c | $(BUILD_DIR) limine-setup - @printf "$(YELLOW)[CC]$(RESET)[mem] $< -> $@" - mkdir -p $(dir $@) - $(CC) $(CFLAGS) -c $< -o $@ - -$(BUILD_DIR)/%.o: $(SRC_DIR)/dev/%.c | $(BUILD_DIR) limine-setup - @printf "$(YELLOW)[CC]$(RESET)[dev] $< -> $@" - mkdir -p $(dir $@) - $(CC) $(CFLAGS) -c $< -o $@ - -$(BUILD_DIR)/%.o: $(SRC_DIR)/drivers/%.c | $(BUILD_DIR) limine-setup - @printf "$(YELLOW)[CC]$(RESET)[drivers] $< -> $@" - mkdir -p $(dir $@) - $(CC) $(CFLAGS) -c $< -o $@ - -$(BUILD_DIR)/%.o: $(SRC_DIR)/input/%.c | $(BUILD_DIR) limine-setup - @printf "$(YELLOW)[CC]$(RESET)[input] $< -> $@" - mkdir -p $(dir $@) - $(CC) $(CFLAGS) -c $< -o $@ - -$(BUILD_DIR)/%.o: $(SRC_DIR)/net/%.c | $(BUILD_DIR) limine-setup - @printf "$(YELLOW)[CC]$(RESET)[net] $< -> $@" - mkdir -p $(dir $@) - $(CC) $(CFLAGS) -c $< -o $@ - -$(BUILD_DIR)/%.o: $(SRC_DIR)/net/nic/%.c | $(BUILD_DIR) limine-setup - @printf "$(YELLOW)[CC]$(RESET)[net/nic] $< -> $@" - mkdir -p $(dir $@) - $(CC) $(CFLAGS) -c $< -o $@ - -$(BUILD_DIR)/%.o: $(SRC_DIR)/fs/%.c | $(BUILD_DIR) limine-setup - @printf "$(YELLOW)[CC]$(RESET)[fs] $< -> $@" - mkdir -p $(dir $@) - $(CC) $(CFLAGS) -c $< -o $@ - -$(BUILD_DIR)/%.o: $(SRC_DIR)/wm/%.c | $(BUILD_DIR) limine-setup - @printf "$(YELLOW)[CC]$(RESET)[wm] $< -> $@" - mkdir -p $(dir $@) - $(CC) $(CFLAGS) -c $< -o $@ - -$(BUILD_DIR)/lwip/%.o: $(SRC_DIR)/net/third_party/lwip/%.c | $(BUILD_DIR) limine-setup - @printf "$(YELLOW)[CC]$(RESET)[lwIP] $< -> $@" - mkdir -p $(dir $@) - $(CC) $(CFLAGS) -c $< -o $@ - -$(BUILD_DIR)/%.o: $(SRC_DIR)/arch/%.asm | $(BUILD_DIR) - @printf "$(YELLOW)[ASM]$(RESET) $< -> $@" +$(BUILD_DIR)/%.o: $(SRC_DIR)/%.asm | $(BUILD_DIR) + @printf "$(YELLOW)[ASM]$(RESET) $< -> $@\n" + @mkdir -p $(dir $@) $(NASM) $(NASMFLAGS) $< -o $@ -$(BUILD_DIR)/test_syscall.o: $(SRC_DIR)/arch/test_syscall.asm | $(BUILD_DIR) - @printf "$(YELLOW)[ASM][test_syscall]$(RESET) $< -> $@" - $(NASM) $(NASMFLAGS) $< -o $@ - -$(BUILD_DIR)/user_test.o: $(SRC_DIR)/arch/user_test.asm | $(BUILD_DIR) - @printf "$(YELLOW)[ASM][user_test]$(RESET) $< -> $@" - $(NASM) $(NASMFLAGS) $< -o $@ - -$(BUILD_DIR)/process_asm.o: $(SRC_DIR)/arch/process_asm.asm | $(BUILD_DIR) - @printf "$(YELLOW)[ASM][process]$(RESET) $< -> $@" - $(NASM) $(NASMFLAGS) $< -o $@ $(KERNEL_ELF): $(OBJ_FILES) $(call PRINT_STEP,LINKING KERNEL) - @printf "$(YELLOW)[LD]$(RESET) Linking kernel ELF: $@" + @printf "$(YELLOW)[LD]$(RESET) Linking kernel ELF: $@\n" $(LD) $(LDFLAGS) -o $@ $(OBJ_FILES) - @printf "$(GREEN)[OK]$(RESET) Kernel ELF built: $@" + @printf "$(GREEN)[OK]$(RESET) Kernel ELF built: $@\n" $(call PRINT_STEP,BUILDING USERLAND) $(MAKE) -C $(SRC_DIR)/userland - @printf "$(GREEN)[OK]$(RESET) Userland build complete." + @printf "$(GREEN)[OK]$(RESET) Userland build complete.\n" $(BUILD_DIR)/initrd.tar: $(KERNEL_ELF) $(call PRINT_STEP,BUILDING INITRD) - @printf "$(YELLOW)[INITRD]$(RESET) Cleaning previous initrd directory..." + @printf "$(YELLOW)[INITRD]$(RESET) Cleaning previous initrd directory...\n" rm -rf $(BUILD_DIR)/initrd - @printf "$(YELLOW)[INITRD]$(RESET) Creating directory structure..." + @printf "$(YELLOW)[INITRD]$(RESET) Creating directory structure...\n" mkdir -p $(BUILD_DIR)/initrd/bin mkdir -p $(BUILD_DIR)/initrd/Library/images/Wallpapers mkdir -p $(BUILD_DIR)/initrd/Library/images/gif @@ -232,21 +144,21 @@ $(BUILD_DIR)/initrd.tar: $(KERNEL_ELF) mkdir -p $(BUILD_DIR)/initrd/usr/include/libc mkdir -p $(BUILD_DIR)/initrd/usr/lib - @printf "$(YELLOW)[COPY]$(RESET) Limine binaries + kernel for installer..." - @if [ -f limine/BOOTX64.EFI ]; then cp limine/BOOTX64.EFI $(BUILD_DIR)/initrd/boot/; fi + @printf "$(YELLOW)[COPY]$(RESET) Limine binaries + kernel for installer...\n" + @if [ -f limine/BOOTX64.EFI ]; then cp limine/BOOTX64.EFI $(BUILD_DIR)/initrd/boot/; fi @if [ -f limine/BOOTIA32.EFI ]; then cp limine/BOOTIA32.EFI $(BUILD_DIR)/initrd/boot/; fi @if [ -f limine/limine-bios.sys ]; then cp limine/limine-bios.sys $(BUILD_DIR)/initrd/boot/; fi @cp $(KERNEL_ELF) $(BUILD_DIR)/initrd/boot/boredos.elf - @printf "$(YELLOW)[COPY]$(RESET) Userland binaries..." + @printf "$(YELLOW)[COPY]$(RESET) Userland binaries...\n" @for f in $(SRC_DIR)/userland/bin/*.elf; do \ if [ -f "$$f" ]; then \ - printf " -> $$f"; \ + printf " -> $$f\n"; \ cp "$$f" $(BUILD_DIR)/initrd/bin/; \ fi \ done - @printf "$(YELLOW)[COPY]$(RESET) TCC support files..." + @printf "$(YELLOW)[COPY]$(RESET) TCC support files...\n" @cp $(SRC_DIR)/userland/cli/third_party/tcc/libtcc1.a $(BUILD_DIR)/initrd/usr/lib/tcc/ @cp $(SRC_DIR)/userland/cli/third_party/tcc/libtcc1.a $(BUILD_DIR)/initrd/usr/lib/ @cp $(SRC_DIR)/userland/cli/third_party/tcc/include/*.h $(BUILD_DIR)/initrd/usr/lib/tcc/include/ @@ -263,120 +175,120 @@ $(BUILD_DIR)/initrd.tar: $(KERNEL_ELF) @cp $(SRC_DIR)/userland/libc/*.h $(BUILD_DIR)/initrd/usr/local/include/ @cp $(SRC_DIR)/userland/stb_image.h $(BUILD_DIR)/initrd/usr/include/ - @printf "$(YELLOW)[COPY]$(RESET) Wallpapers..." + @printf "$(YELLOW)[COPY]$(RESET) Wallpapers...\n" @for f in $(SRC_DIR)/images/wallpapers/*; do \ if [ -f "$$f" ]; then \ - printf " -> $$f"; \ + printf " -> $$f\n"; \ cp "$$f" $(BUILD_DIR)/initrd/Library/images/Wallpapers/; \ fi \ done - @printf "$(YELLOW)[COPY]$(RESET) GIF assets..." + @printf "$(YELLOW)[COPY]$(RESET) GIF assets...\n" @for f in $(SRC_DIR)/images/gif/*.gif; do \ if [ -f "$$f" ]; then \ - printf " -> $$f"; \ + printf " -> $$f\n"; \ cp "$$f" $(BUILD_DIR)/initrd/Library/images/gif/; \ fi \ done - @printf "$(YELLOW)[COPY]$(RESET) Colloid icons..." + @printf "$(YELLOW)[COPY]$(RESET) Colloid icons...\n" @for f in $(COLLOID_ICONS); do \ src="$(SRC_DIR)/images/icons/colloid/$$f"; \ if [ -f "$$src" ]; then \ - printf " -> $$src"; \ + printf " -> $$src\n"; \ cp "$$src" $(BUILD_DIR)/initrd/Library/images/icons/colloid/; \ fi \ done - @printf "$(YELLOW)[COPY]$(RESET) BoredOS icons..." + @printf "$(YELLOW)[COPY]$(RESET) BoredOS icons...\n" @mkdir -p $(BUILD_DIR)/initrd/Library/images/icons/boredos @for f in $(SRC_DIR)/images/icons/boredos/*.png; do \ if [ -f "$$f" ]; then \ - printf " -> $$f"; \ + printf " -> $$f\n"; \ cp "$$f" $(BUILD_DIR)/initrd/Library/images/icons/boredos/; \ fi \ done - @printf "$(YELLOW)[COPY]$(RESET) Branding assets..." + @printf "$(YELLOW)[COPY]$(RESET) Branding assets...\n" @cp -r branding/* $(BUILD_DIR)/initrd/Library/images/branding/ - @printf "$(YELLOW)[COPY]$(RESET) Fonts..." + @printf "$(YELLOW)[COPY]$(RESET) Fonts...\n" @for f in $(SRC_DIR)/fonts/*.ttf; do \ if [ -f "$$f" ]; then \ - printf " -> $$f"; \ + printf " -> $$f\n"; \ cp "$$f" $(BUILD_DIR)/initrd/Library/Fonts/; \ fi \ done - @printf "$(YELLOW)[COPY]$(RESET) Emoji fonts..." + @printf "$(YELLOW)[COPY]$(RESET) Emoji fonts...\n" @for f in $(SRC_DIR)/fonts/Emoji/*.ttf; do \ if [ -f "$$f" ]; then \ - printf " -> $$f"; \ + printf " -> $$f\n"; \ cp "$$f" $(BUILD_DIR)/initrd/Library/Fonts/Emoji/; \ fi \ done - @printf "$(YELLOW)[COPY]$(RESET) bsh configuration..." - @if [ -f $(SRC_DIR)/library/bsh/bshrc ]; then printf " -> bshrc"; cp $(SRC_DIR)/library/bsh/bshrc $(BUILD_DIR)/initrd/Library/bsh/; fi - @if [ -f $(SRC_DIR)/library/bsh/startup.bsh ]; then printf " -> startup.bsh"; cp $(SRC_DIR)/library/bsh/startup.bsh $(BUILD_DIR)/initrd/Library/bsh/; fi - @if [ -f $(SRC_DIR)/library/bsh/boot.bsh ]; then printf " -> boot.bsh"; cp $(SRC_DIR)/library/bsh/boot.bsh $(BUILD_DIR)/initrd/Library/bsh/; fi - @if [ -f $(SRC_DIR)/library/conf/sysfetch.cfg ]; then printf " -> sysfetch.cfg"; cp $(SRC_DIR)/library/conf/sysfetch.cfg $(BUILD_DIR)/initrd/Library/conf/; fi + @printf "$(YELLOW)[COPY]$(RESET) bsh configuration...\n" + @if [ -f $(SRC_DIR)/library/bsh/bshrc ]; then printf " -> bshrc\n"; cp $(SRC_DIR)/library/bsh/bshrc $(BUILD_DIR)/initrd/Library/bsh/; fi + @if [ -f $(SRC_DIR)/library/bsh/startup.bsh ]; then printf " -> startup.bsh\n"; cp $(SRC_DIR)/library/bsh/startup.bsh $(BUILD_DIR)/initrd/Library/bsh/; fi + @if [ -f $(SRC_DIR)/library/bsh/boot.bsh ]; then printf " -> boot.bsh\n"; cp $(SRC_DIR)/library/bsh/boot.bsh $(BUILD_DIR)/initrd/Library/bsh/; fi + @if [ -f $(SRC_DIR)/library/conf/sysfetch.cfg ]; then printf " -> sysfetch.cfg\n"; cp $(SRC_DIR)/library/conf/sysfetch.cfg $(BUILD_DIR)/initrd/Library/conf/; fi - @printf "$(YELLOW)[COPY]$(RESET) DOOM assets..." - @if [ -f $(SRC_DIR)/userland/games/doom/doom1.wad ]; then printf " -> doom1.wad"; cp $(SRC_DIR)/userland/games/doom/doom1.wad $(BUILD_DIR)/initrd/Library/DOOM/; fi + @printf "$(YELLOW)[COPY]$(RESET) DOOM assets...\n" + @if [ -f $(SRC_DIR)/userland/games/doom/doom1.wad ]; then printf " -> doom1.wad\n"; cp $(SRC_DIR)/userland/games/doom/doom1.wad $(BUILD_DIR)/initrd/Library/DOOM/; fi - @printf "$(YELLOW)[COPY]$(RESET) ASCII art..." - @if [ -f $(SRC_DIR)/library/art/boredos.txt ]; then printf " -> boredos.txt"; cp $(SRC_DIR)/library/art/boredos.txt $(BUILD_DIR)/initrd/Library/art/; fi + @printf "$(YELLOW)[COPY]$(RESET) ASCII art...\n" + @if [ -f $(SRC_DIR)/library/art/boredos.txt ]; then printf " -> boredos.txt\n"; cp $(SRC_DIR)/library/art/boredos.txt $(BUILD_DIR)/initrd/Library/art/; fi - @printf "$(YELLOW)[COPY]$(RESET) Documentation..." + @printf "$(YELLOW)[COPY]$(RESET) Documentation...\n" @for f in $$(find docs -name '*.md' 2>/dev/null); do \ if [ -f "$$f" ]; then \ - printf " -> $$f"; \ + printf " -> $$f\n"; \ dir=$$(dirname "$$f"); \ mkdir -p $(BUILD_DIR)/initrd/"$$dir"; \ cp "$$f" $(BUILD_DIR)/initrd/"$$dir"/; \ fi \ done - @printf "$(YELLOW)[COPY]$(RESET) Root files..." - @if [ -f README.md ]; then printf " -> README.md"; cp README.md $(BUILD_DIR)/initrd/; fi - @if [ -f LICENSE ]; then printf " -> LICENSE"; cp LICENSE $(BUILD_DIR)/initrd/; fi - @if [ -f limine.conf ]; then printf " -> limine.conf"; cp limine.conf $(BUILD_DIR)/initrd/; fi + @printf "$(YELLOW)[COPY]$(RESET) Root files...\n" + @if [ -f README.md ]; then printf " -> README.md\n"; cp README.md $(BUILD_DIR)/initrd/; fi + @if [ -f LICENSE ]; then printf " -> LICENSE\n"; cp LICENSE $(BUILD_DIR)/initrd/; fi + @if [ -f limine.conf ]; then printf " -> limine.conf\n"; cp limine.conf $(BUILD_DIR)/initrd/; fi - @printf "$(YELLOW)[TAR]$(RESET) Creating initrd.tar..." + @printf "$(YELLOW)[TAR]$(RESET) Creating initrd.tar...\n" cd $(BUILD_DIR)/initrd && COPYFILE_DISABLE=1 tar --exclude="._*" -cf ../initrd.tar * - @printf "$(GREEN)[OK]$(RESET) Initrd created: $(BUILD_DIR)/initrd.tar" + @printf "$(GREEN)[OK]$(RESET) Initrd created: $(BUILD_DIR)/initrd.tar\n" $(ISO_IMAGE): $(KERNEL_ELF) $(BUILD_DIR)/initrd.tar limine.conf limine-setup $(call PRINT_STEP,CREATING ISO IMAGE) - @printf "$(YELLOW)[ISO]$(RESET) Cleaning previous ISO root..." + @printf "$(YELLOW)[ISO]$(RESET) Cleaning previous ISO root...\n" rm -rf $(ISO_DIR) - @printf "$(YELLOW)[ISO]$(RESET) Creating ISO directory structure..." + @printf "$(YELLOW)[ISO]$(RESET) Creating ISO directory structure...\n" mkdir -p $(ISO_DIR) mkdir -p $(ISO_DIR)/EFI/BOOT - @printf "$(YELLOW)[COPY]$(RESET) Kernel ELF..." + @printf "$(YELLOW)[COPY]$(RESET) Kernel ELF...\n" cp $(KERNEL_ELF) $(ISO_DIR)/ - @printf "$(YELLOW)[COPY]$(RESET) Limine config..." + @printf "$(YELLOW)[COPY]$(RESET) Limine config...\n" cp limine.conf $(ISO_DIR)/ - @printf "$(YELLOW)[COPY]$(RESET) Initrd..." + @printf "$(YELLOW)[COPY]$(RESET) Initrd...\n" cp $(BUILD_DIR)/initrd.tar $(ISO_DIR)/ - @printf "$(YELLOW)[CONFIG]$(RESET) Adding initrd module path..." - printf " module_path: boot():/initrd.tar" >> $(ISO_DIR)/limine.conf + @printf "$(YELLOW)[CONFIG]$(RESET) Adding initrd module path...\n" + printf " module_path: boot():/initrd.tar\n" >> $(ISO_DIR)/limine.conf - @printf "$(YELLOW)[COPY]$(RESET) Optional splash image..." - @if [ -f branding/splash.jpg ]; then printf " -> splash.jpg"; cp branding/splash.jpg $(ISO_DIR)/splash.jpg; else printf " -> no splash.jpg found"; fi + @printf "$(YELLOW)[COPY]$(RESET) Optional splash image...\n" + @if [ -f branding/splash.jpg ]; then printf " -> splash.jpg\n"; cp branding/splash.jpg $(ISO_DIR)/splash.jpg; else printf " -> no splash.jpg found\n"; fi - @printf "$(YELLOW)[COPY]$(RESET) Limine boot files..." + @printf "$(YELLOW)[COPY]$(RESET) Limine boot files...\n" cp limine/limine-bios.sys $(ISO_DIR)/ cp limine/limine-bios-cd.bin $(ISO_DIR)/ cp limine/limine-uefi-cd.bin $(ISO_DIR)/ - @printf "$(YELLOW)[COPY]$(RESET) EFI bootloaders..." + @printf "$(YELLOW)[COPY]$(RESET) EFI bootloaders...\n" cp limine/BOOTX64.EFI $(ISO_DIR)/EFI/BOOT/ cp limine/BOOTIA32.EFI $(ISO_DIR)/EFI/BOOT/ @@ -387,15 +299,15 @@ $(ISO_IMAGE): $(KERNEL_ELF) $(BUILD_DIR)/initrd.tar limine.conf limine-setup -efi-boot-part --efi-boot-image --protective-msdos-label \ $(ISO_DIR) -o $(ISO_IMAGE) - @printf "$(YELLOW)[LIMINE]$(RESET) Installing BIOS bootloader..." + @printf "$(YELLOW)[LIMINE]$(RESET) Installing BIOS bootloader...\n" ./limine/limine bios-install $(ISO_IMAGE) - @printf "$(GREEN)[OK]$(RESET) ISO image ready: $(ISO_IMAGE)" + @printf "$(GREEN)[OK]$(RESET) ISO image ready: $(ISO_IMAGE)\n" clean: $(call PRINT_STEP,CLEANING BUILD OUTPUT) rm -rf $(BUILD_DIR) $(ISO_DIR) $(ISO_IMAGE) $(MAKE) -C $(SRC_DIR)/userland clean - @printf "$(GREEN)[OK]$(RESET) Clean complete." + @printf "$(GREEN)[OK]$(RESET) Clean complete.\n" disk.qcow2: $(call PRINT_STEP,CREATING 10GB EXPANDABLE DISK IMAGE) @@ -456,7 +368,7 @@ endif $(OVMF_VARS): @if [ -f $(OVMF_VARS_TMPL) ]; then \ - printf "$(YELLOW)[UEFI]$(RESET) Creating local NVRAM vars..."; \ + printf "$(YELLOW)[UEFI]$(RESET) Creating local NVRAM vars...\n"; \ cp $(OVMF_VARS_TMPL) $(OVMF_VARS); \ fi diff --git a/README.md b/README.md index 01948ab..4f9d72a 100644 --- a/README.md +++ b/README.md @@ -67,175 +67,6 @@ --- -## Contributors - -
- BoredOS Logo - -

A modern x86_64 hobbyist operating system built from the ground up.

- - [![License: GPL v3](https://img.shields.io/badge/License-GPLv3-blue.svg)](https://www.gnu.org/licenses/gpl-3.0) - ![Platform: x86_64](https://img.shields.io/badge/Platform-x86_64-lightgrey) - ![Status: Active](https://img.shields.io/badge/Status-Active-brightgreen) - ![GitHub all releases](https://img.shields.io/github/downloads/boreddevnl/BoredOS/total?color=brightgreen) - -
- - [Docs](docs/README.md) · [Build & Run](docs/build/usage.md) · [AppDev SDK](docs/appdev/sdk_reference.md) · [Discord](https://discord.gg/J2BxWaFAgY) · [Support](https://buymeacoffee.com/boreddevhq) - -
- ---- - -![Screenshot](branding/screenshot.jpg) - -> [!NOTE] -> The screenshot above may represent a previous build and is subject to change as the UI evolves. - ---- - -## Features - -### Kernel and Architecture -- **Long Mode Architecture** — Native x86_64 implementation utilizing 64-bit address space and registers -- **Symmetric Multi-Processing** — Scalable multi-core support with IPI-based scheduling and synchronization -- **Advanced Memory Management** — Custom slab allocator with object pooling and efficient physical/virtual page mapping -- **Hybrid VFS Layer** — Unified filesystem interface supporting FAT32, TAR, ProcFS, and SysFS -- **Preemptive Multitasking** — Prioritized process scheduling with full context isolation -- **Hardware Abstraction** — Comprehensive driver support for PCI, AHCI, PS/2, and ACPI - -### Graphical Desktop Environment -- **BoredWM** — High-performance window manager featuring window stacking, focus management, and drag-and-drop interactions -- **Typography Engine** — Integrated font manager with TrueType (TTF) support and efficient glyph caching -- **Rich Media Subsystem** — Native hardware-independent decoding for PNG, JPEG, GIF, BMP, and TGA formats -- **LibWidget Toolkit** — Native UI component library for rapid application development - -### Networking Stack -- **TCP/IP Integration** — Full lwIP-based network stack featuring DHCP, DNS, and Berkeley-style sockets -- **Network Services** — Integrated support for basic web browsing and real-time network telemetry - -### Application Ecosystem -| Category | Applications | -|----------|--------------| -| Productivity | Text Editor, Markdown Viewer, BoredWord Processor, Web Browser, Calculator | -| Development | TCC (Tiny C Compiler), Lua| -| System | Explorer (File Manager), Task Manager, System Monitor, Graphing Utility | -| Games | doomgeneric, Minesweeper, 2048, Snake | - - ---- - - - -## 📚 Documentation - -| Guide | Description | -|-------|-------------| -| [Documentation Index](docs/README.md) | Start here! | -| [Architecture Overview](docs/architecture/README.md) | Deep dive into the kernel | -| [Building and Running](docs/build/usage.md) | Set up your build environment | -| [AppDev SDK](docs/appdev/custom_apps.md) | Build your own apps for BoredOS | - ---- - -## Contributors - - - - - - - - - - - - - - -
- -
- BoredDevNL -

- Creator & Lead Maintainer -
- -
- Lluciocc -

- Maintainer -
- -
- Mellurboo -

- Contributor -
- -
- Artemix1508 -

- Artwork -
- -
- Zeyadhost -

- Contributor -
- -
- Naplon74 -

- Artwork -
- -
- pixelyblah -

- Artwork -
- -
- qwroffc -

- Artwork -
- ---- - -## ☕ Support the Journey - -If you find BoredOS interesting or useful, consider fueling development with a coffee! - - - Buy Me A Coffee - - ---- - -## History - -**BoredOS** is the successor to **[BrewKernel](https://github.com/boreddevnl/brewkernel)**, a project started in 2023. BrewKernel served as the foundational learning ground but has since been officially deprecated and archived — it no longer receives updates, bug fixes, or pull request reviews. - -BoredOS is a complete architectural reboot, applying years of lessons learned to build a cleaner, more modular, and more capable system. - -> [!IMPORTANT] -> Please direct all issues, discussions, and contributions to this repository. Legacy BrewKernel code is preserved for historical purposes only and is not compatible with BoredOS. - ---- - -## License - -**Copyright (C) 2023–2026 boreddevnl** - -Distributed under the **GNU General Public License v3**. See [`LICENSE`](LICENSE) for details. - -> [!IMPORTANT] -> You must retain all copyright headers and include the original attribution in any redistributions or derivative works. See the [`NOTICE`](NOTICE) file for more details. - ---- ## ☕ Support the Journey diff --git a/docs/usage/terminal.md b/docs/usage/terminal.md index 3ee2b96..8cf7b37 100644 --- a/docs/usage/terminal.md +++ b/docs/usage/terminal.md @@ -7,11 +7,88 @@ The BoredOS Terminal provides a powerful command-line interface (CLI) for advanc The default shell in BoredOS is **BoredShell (Bsh)**, a userspace shell with a dedicated terminal app. It features: - **ANSI Color Support**: Rich text output with colors and styles. - **Command History**: Use the **Up** and **Down** arrow keys to navigate through your previous commands (up to 64 history entries). -- **Output Redirection**: +- **Redirection**: - `command > file`: Write output to a new file (or overwrite existing). - `command >> file`: Append output to an existing file. + - `command < file`: Use a file as command input. - **Piping**: - `command1 | command2`: Pass the output of the first command as input to the second. +- **Command Chaining**: + - `cmd1 ; cmd2`: Run `cmd2` after `cmd1`. + - `cmd1 && cmd2`: Run `cmd2` only if `cmd1` succeeds. + - `cmd1 || cmd2`: Run `cmd2` only if `cmd1` fails. +- **Background Launch**: + - `command &`: Start command without blocking the prompt. + +## Shell Operators & Scripting + +Bsh provides a suite of operators for managing I/O and controlling execution flow. + +### I/O Redirection + +Redirection allows you to change where a command reads its input from or writes its output to by manipulating kernel-level file descriptors (FDs). + +| Operator | Action | Description | +| :--- | :--- | :--- | +| `>` | **Overwrite** | Redirects standard output (FD 1) to a file, creating it if it doesn't exist or truncating it if it does. | +| `>>` | **Append** | Redirects standard output (FD 1) to a file, appending new data to the end without clearing existing content. | +| `<` | **Input** | Redirects standard input (FD 0) to read from a file instead of the terminal. | + +**Example:** +```bash +echo "Hello World" > greetings.txt +echo "Second line" >> greetings.txt +cat < greetings.txt +``` + +**How it works:** +The shell uses `sys_open` to obtain a handle for the target file, then uses `sys_dup2` to replace the process's standard FD (0 or 1) with the new file handle. This ensures the command's standard library calls (like `printf` or `scanf`) interact with the file instead of the terminal. + +### Pipelines (`|`) + +Pipelines connect the output of one command directly to the input of another. + +**Usage:** `cmd1 | cmd2 | cmd3` + +**Example:** +```bash +ls /bin | cat | cat +``` + +**How it works:** +The shell creates an anonymous pipe using `sys_pipe`, which returns two FDs: a read end and a write end. +- The shell duplicates the **write end** to FD 1 for the first command. +- The shell duplicates the **read end** to FD 0 for the second command. +- Both commands run in parallel, and the kernel manages the data buffer between them. + +### Execution Control + +| Operator | Name | Description | +| :--- | :--- | :--- | +| `;` | **Semicolon** | Executes commands sequentially. `cmd2` runs only after `cmd1` finishes. | +| `&&` | **Logical AND** | Executes `cmd2` only if `cmd1` returns a success status (exit code 0). | +| `||` | **Logical OR** | Executes `cmd2` only if `cmd1` fails (returns a non-zero exit code). | + +**Example:** +```bash +# Compile and run only on success +make && ./boredos.elf + +# Recover from a missing command +missing_tool || echo "Tool not found, using fallback" +``` + +### Background Execution (`&`) + +Appending `&` to a command tells the shell not to wait for the process to complete before returning to the prompt. + +**How it works:** +Normally, the shell calls `sys_waitpid` to block until a child process exits. With `&`, the shell skips this wait, allowing the process to run asynchronously while you continue using the terminal. + +Operator precedence follows common POSIX shell rules: +1. Redirection and pipelines (`<`, `>`, `>>`, `|`) +2. Conditionals (`&&`, `||`) +3. List separators (`;`, `&`) ### Bsh Configuration @@ -49,13 +126,12 @@ Below are some of the most used commands available in `/bin`: | `ls` | List files and directories in the current path. | | `cd` | Change the current working directory. | | `cat` | Display the contents of a file. | -| `ls` | List directory contents. | | `rm` | Remove a file. | | `mkdir` | Create a new directory. | | `man` | View the manual for a specific command (e.g., `man ls`). | -| `lsblk` | List block devices and partitions with size, type, filesystem, label, and flags. | -| `du` | Report disk usage for files and directories, recursively. | -| `sysfetch` | Display system and hardware information. | +| `lsblk` | List block devices and partitions. | +| `du` | Report disk usage for files and directories. | +| `sysfetch` | Display system information and BoredOS branding. | --- diff --git a/src/images/wallpapers/Drift.png b/src/images/wallpapers/Drift.png new file mode 100644 index 0000000..9faa635 Binary files /dev/null and b/src/images/wallpapers/Drift.png differ diff --git a/src/sys/process.c b/src/sys/process.c index a0fe9aa..0b57d2c 100644 --- a/src/sys/process.c +++ b/src/sys/process.c @@ -302,6 +302,40 @@ process_t* process_create_elf(const char* filepath, const char* args_str, bool t new_proc->fd_kind[i] = 0; new_proc->fd_flags[i] = 0; } + + process_t *parent = process_get_current(); + if (parent) { + for (int i = 0; i < MAX_PROCESS_FDS; i++) { + if (parent->fds[i]) { + new_proc->fds[i] = parent->fds[i]; + new_proc->fd_kind[i] = parent->fd_kind[i]; + new_proc->fd_flags[i] = parent->fd_flags[i]; + + if (new_proc->fd_kind[i] == PROC_FD_KIND_FILE) { + process_fd_file_ref_t *ref = (process_fd_file_ref_t *)new_proc->fds[i]; + if (ref) ref->refs++; + } else if (new_proc->fd_kind[i] == PROC_FD_KIND_PIPE_READ) { + process_fd_pipe_t *pipe = (process_fd_pipe_t *)new_proc->fds[i]; + if (pipe) pipe->readers++; + } else if (new_proc->fd_kind[i] == PROC_FD_KIND_PIPE_WRITE) { + process_fd_pipe_t *pipe = (process_fd_pipe_t *)new_proc->fds[i]; + if (pipe) pipe->writers++; + } + } + } + } + + // Always set up TTY FDs if a TTY is provided and they aren't already set + if (tty_id >= 0) { + for (int i = 0; i < 3; i++) { + if (!new_proc->fds[i]) { + new_proc->fds[i] = (void*)(uint64_t)1; + new_proc->fd_kind[i] = PROC_FD_KIND_TTY; + new_proc->fd_flags[i] = (i == 0) ? 0 : 1; + } + } + } + new_proc->gui_event_head = 0; new_proc->gui_event_tail = 0; new_proc->ui_window = NULL; @@ -314,7 +348,6 @@ process_t* process_create_elf(const char* filepath, const char* args_str, bool t new_proc->exit_status = 0; process_init_signal_state(new_proc); - process_t *parent = process_get_current(); if (parent) { extern void mem_memcpy(void *dest, const void *src, size_t len); mem_memcpy(new_proc->cwd, parent->cwd, 1024); diff --git a/src/sys/process.h b/src/sys/process.h index cd3f9ed..ccbdb95 100644 --- a/src/sys/process.h +++ b/src/sys/process.h @@ -17,6 +17,7 @@ #define PROC_FD_KIND_FILE 1 #define PROC_FD_KIND_PIPE_READ 2 #define PROC_FD_KIND_PIPE_WRITE 3 +#define PROC_FD_KIND_TTY 4 typedef struct { void *file; diff --git a/src/sys/syscall.c b/src/sys/syscall.c index fe53cab..951683b 100644 --- a/src/sys/syscall.c +++ b/src/sys/syscall.c @@ -1102,6 +1102,12 @@ static uint64_t fs_cmd_read(const syscall_args_t *args) { return n; } + if (proc->fd_kind[fd] == PROC_FD_KIND_TTY) { + if (proc->tty_id < 0) return (uint64_t)-1; + extern int tty_read_input(int tty_id, char *buf, size_t len); + return (uint64_t)tty_read_input(proc->tty_id, (char *)buf, (size_t)len); + } + return -1; } @@ -1139,6 +1145,12 @@ static uint64_t fs_cmd_write(const syscall_args_t *args) { return n; } + if (proc->fd_kind[fd] == PROC_FD_KIND_TTY) { + if (proc->tty_id < 0) return (uint64_t)-1; + extern int tty_write_output(int tty_id, const char *buf, size_t len); + return (uint64_t)tty_write_output(proc->tty_id, (const char *)buf, (size_t)len); + } + return -1; } diff --git a/src/userland/cli/bsh.c b/src/userland/cli/bsh.c index 5f329df..5e8262e 100644 --- a/src/userland/cli/bsh.c +++ b/src/userland/cli/bsh.c @@ -2,6 +2,8 @@ // This software is released under the GNU General Public License v3.0. See LICENSE file for details. // This header needs to maintain in any file it is present in, as per the GPL license terms. #include +#include +#include #include #include #include @@ -327,6 +329,17 @@ static void reset_color(void) { sys_set_text_color(g_color_default); } +static void shell_write(const char *buf, int len) { + if (!buf || len <= 0) return; + write(1, buf, (size_t)len); +} + +static void shell_writeln(const char *buf) { + if (!buf) return; + shell_write(buf, (int)strlen(buf)); + shell_write("\n", 1); +} + static void prompt_emit(const char *text, int len, char *out, int *out_idx, int max_len, bool do_write) { if (!text || len <= 0) return; if (do_write) sys_write(1, text, len); @@ -986,20 +999,40 @@ static void show_matches(const char *prompt_tmpl, const char *line, int len, cha static int wait_for_pid_status(int pid, int *status) { while (1) { int child_status = 0; + int rc = sys_waitpid(pid, &child_status, 1); + if (rc == pid) { if (status) *status = child_status; return 0; } + if (rc < 0) return -1; + if (g_tty_id >= 0) { int fg = sys_tty_get_fg(g_tty_id); if (fg != pid) return -1; } + sleep(10); } } +static int wait_for_pid(int pid) { + int status = 0; + + if (wait_for_pid_status(pid, &status) != 0) + return -1; + + return status; +} + +static int bsh_open_file(const char *path, const char *mode, bool *is_kernel) { + if (!path || !mode) return -1; + if (is_kernel) *is_kernel = true; + return sys_open(path, mode); +} + static void wait_for_pid(int pid) { wait_for_pid_status(pid, NULL); } @@ -1136,7 +1169,7 @@ static int builtin_cd(int argc, char *argv[]) { static int builtin_pwd(void) { char cwd[256]; if (getcwd(cwd, sizeof(cwd))) { - printf("%s\n", cwd); + shell_writeln(cwd); return 0; } return 1; @@ -1144,10 +1177,10 @@ static int builtin_pwd(void) { static int builtin_echo(int argc, char *argv[]) { for (int i = 1; i < argc; i++) { - if (i > 1) sys_write(1, " ", 1); - sys_write(1, argv[i], (int)strlen(argv[i])); + if (i > 1) shell_write(" ", 1); + shell_write(argv[i], (int)strlen(argv[i])); } - sys_write(1, "\n", 1); + shell_write("\n", 1); return 0; } @@ -1165,7 +1198,7 @@ static int builtin_ls(int argc, char *argv[]) { } if (!info.is_directory) { set_color(g_color_file); - printf("%s\n", info.name); + shell_writeln(info.name); reset_color(); return 0; } @@ -1182,12 +1215,18 @@ static int builtin_ls(int argc, char *argv[]) { for (int i = 0; i < count; i++) { if (entries[i].is_directory) { set_color(g_color_dir); - printf("[DIR] %s\n", entries[i].name); + shell_write("[DIR] ", 7); + shell_writeln(entries[i].name); } else { set_color(g_color_file); - printf("[FILE] %s", entries[i].name); + shell_write("[FILE] ", 7); + shell_write(entries[i].name, (int)strlen(entries[i].name)); set_color(g_color_size); - printf(" (%d bytes)\n", entries[i].size); + char size_buf[32]; + itoa((int)entries[i].size, size_buf); + shell_write(" (", 2); + shell_write(size_buf, (int)strlen(size_buf)); + shell_write(" bytes)\n", 8); } } reset_color(); @@ -1196,10 +1235,12 @@ static int builtin_ls(int argc, char *argv[]) { static int builtin_cat(int argc, char *argv[]) { if (argc < 2) { - set_color(g_color_error); - printf("Usage: cat \n"); - reset_color(); - return 1; + char buffer[4096]; + int bytes; + while ((bytes = read(0, buffer, sizeof(buffer))) > 0) { + shell_write(buffer, bytes); + } + return 0; } for (int i = 1; i < argc; i++) { @@ -1213,7 +1254,7 @@ static int builtin_cat(int argc, char *argv[]) { char buffer[4096]; int bytes; while ((bytes = sys_read(fd, buffer, sizeof(buffer))) > 0) { - sys_write(1, buffer, bytes); + shell_write(buffer, bytes); } sys_close(fd); } @@ -1439,17 +1480,20 @@ static int builtin_man(int argc, char *argv[]) { char buffer[4096]; int bytes; while ((bytes = sys_read(fd, buffer, sizeof(buffer))) > 0) { - sys_write(1, buffer, bytes); + shell_write(buffer, bytes); } sys_close(fd); - printf("\n"); + shell_write("\n", 1); return 0; } static int builtin_alias(int argc, char *argv[]) { if (argc == 1) { for (int i = 0; i < g_alias_count; i++) { - printf("alias %s=%s\n", g_aliases[i].name, g_aliases[i].value); + shell_write("alias ", 6); + shell_write(g_aliases[i].name, (int)strlen(g_aliases[i].name)); + shell_write("=", 1); + shell_writeln(g_aliases[i].value); } return 0; } @@ -1460,7 +1504,10 @@ static int builtin_alias(int argc, char *argv[]) { if (*eq != '=') { const char *val = alias_get(argv[i]); if (val) { - printf("alias %s=%s\n", argv[i], val); + shell_write("alias ", 6); + shell_write(argv[i], (int)strlen(argv[i])); + shell_write("=", 1); + shell_writeln(val); continue; } set_color(g_color_error); @@ -1521,19 +1568,220 @@ static int execute_builtin(int argc, char *argv[]) { return -1; } -static int execute_line_inner(const char *line, int depth) { - if (!line || !line[0]) return 0; - if (depth > 8) return 1; +static bool is_builtin_name(const char *name) { + return str_eq(name, "cd") || str_eq(name, "pwd") || str_eq(name, "ls") || + str_eq(name, "cat") || str_eq(name, "echo") || str_eq(name, "clear") || + str_eq(name, "mkdir") || str_eq(name, "rm") || str_eq(name, "touch") || + str_eq(name, "cp") || str_eq(name, "mv") || str_eq(name, "man") || + str_eq(name, "alias") || str_eq(name, "unalias") || str_eq(name, ".") || + str_eq(name, "exit"); +} - char line_copy[MAX_LINE]; - str_copy(line_copy, line, sizeof(line_copy)); - trim(line_copy); - if (!line_copy[0]) return 0; - if (line_copy[0] == '#') return 0; +typedef enum { + TOK_WORD = 0, + TOK_PIPE, + TOK_GT, + TOK_GTGT, + TOK_LT, + TOK_AMP, + TOK_ANDAND, + TOK_OROR, + TOK_SEMI, + TOK_END +} bsh_tok_type_t; +typedef struct { + bsh_tok_type_t type; + char text[MAX_MATCH_LEN]; +} bsh_token_t; + +typedef struct { char *argv[MAX_ARGS]; - int argc = split_args(line_copy, argv, MAX_ARGS); + int argc; + char *redir_in; + char *redir_out; + bool redir_append; +} bsh_simple_cmd_t; + +#define MAX_TOKENS 128 +#define MAX_PIPE_CMDS 16 + +static bool is_op_char(char c) { + return c == '|' || c == '&' || c == ';' || c == '<' || c == '>'; +} + +static void print_syntax_error(const char *msg) { + set_color(g_color_error); + printf("bsh: syntax error: %s\n", msg); + reset_color(); +} + +static void alias_snapshot_save(alias_t out_aliases[], int *out_count) { + if (!out_aliases || !out_count) return; + *out_count = g_alias_count; + for (int i = 0; i < g_alias_count && i < MAX_ALIASES; i++) { + out_aliases[i] = g_aliases[i]; + } +} + +static void alias_snapshot_restore(const alias_t saved_aliases[], int saved_count) { + g_alias_count = 0; + if (!saved_aliases || saved_count <= 0) return; + int copy_count = saved_count < MAX_ALIASES ? saved_count : MAX_ALIASES; + for (int i = 0; i < copy_count; i++) { + g_aliases[i] = saved_aliases[i]; + } + g_alias_count = copy_count; +} + +static int tokenize_line(const char *line, bsh_token_t toks[], int max_toks) { + if (!line || !toks || max_toks < 2) return -1; + int tcount = 0; + int i = 0; + + while (line[i]) { + while (line[i] == ' ' || line[i] == '\t') i++; + if (!line[i]) break; + if (tcount >= max_toks - 1) { + print_syntax_error("too many tokens"); + return -1; + } + + if (line[i] == '&' && line[i + 1] == '&') { + toks[tcount].type = TOK_ANDAND; + toks[tcount].text[0] = 0; + tcount++; + i += 2; + continue; + } + if (line[i] == '|' && line[i + 1] == '|') { + toks[tcount].type = TOK_OROR; + toks[tcount].text[0] = 0; + tcount++; + i += 2; + continue; + } + if (line[i] == '>' && line[i + 1] == '>') { + toks[tcount].type = TOK_GTGT; + toks[tcount].text[0] = 0; + tcount++; + i += 2; + continue; + } + + if (line[i] == '|') { + toks[tcount].type = TOK_PIPE; + toks[tcount].text[0] = 0; + tcount++; + i++; + continue; + } + if (line[i] == '&') { + toks[tcount].type = TOK_AMP; + toks[tcount].text[0] = 0; + tcount++; + i++; + continue; + } + if (line[i] == ';') { + toks[tcount].type = TOK_SEMI; + toks[tcount].text[0] = 0; + tcount++; + i++; + continue; + } + if (line[i] == '<') { + toks[tcount].type = TOK_LT; + toks[tcount].text[0] = 0; + tcount++; + i++; + continue; + } + if (line[i] == '>') { + toks[tcount].type = TOK_GT; + toks[tcount].text[0] = 0; + tcount++; + i++; + continue; + } + + toks[tcount].type = TOK_WORD; + int out = 0; + while (line[i] && line[i] != ' ' && line[i] != '\t' && !is_op_char(line[i])) { + if (line[i] == '"' || line[i] == '\'') { + char quote = line[i++]; + while (line[i] && line[i] != quote) { + if (out < MAX_MATCH_LEN - 1) toks[tcount].text[out++] = line[i]; + i++; + } + if (line[i] == quote) i++; + continue; + } + if (out < MAX_MATCH_LEN - 1) toks[tcount].text[out++] = line[i]; + i++; + } + toks[tcount].text[out] = 0; + if (out == 0) { + print_syntax_error("unexpected token"); + return -1; + } + tcount++; + } + + toks[tcount].type = TOK_END; + toks[tcount].text[0] = 0; + return tcount; +} + +static bool parse_simple_command(bsh_token_t toks[], int *idx, bsh_simple_cmd_t *cmd) { + if (!toks || !idx || !cmd) return false; + cmd->argc = 0; + cmd->redir_in = NULL; + cmd->redir_out = NULL; + cmd->redir_append = false; + + while (1) { + bsh_tok_type_t t = toks[*idx].type; + if (t == TOK_WORD) { + if (cmd->argc >= MAX_ARGS - 1) { + print_syntax_error("too many arguments"); + return false; + } + cmd->argv[cmd->argc++] = toks[*idx].text; + (*idx)++; + continue; + } + if (t == TOK_LT || t == TOK_GT || t == TOK_GTGT) { + bsh_tok_type_t redir = t; + (*idx)++; + if (toks[*idx].type != TOK_WORD) { + print_syntax_error("missing filename after redirection"); + return false; + } + if (redir == TOK_LT) { + cmd->redir_in = toks[*idx].text; + } else { + cmd->redir_out = toks[*idx].text; + cmd->redir_append = (redir == TOK_GTGT); + } + (*idx)++; + continue; + } + break; + } + + cmd->argv[cmd->argc] = NULL; + if (cmd->argc <= 0) { + print_syntax_error("expected command"); + return false; + } + return true; +} + +static int execute_argv_inner(int argc, char *argv[], int depth, bool isolated, bool background, bool *want_exit, int *out_pid) { if (argc <= 0) return 0; + if (depth > 8) return 1; + if (out_pid) *out_pid = -1; if (!str_eq(argv[0], "alias") && !str_eq(argv[0], "unalias")) { const char *alias_val = alias_get(argv[0]); @@ -1547,13 +1795,37 @@ static int execute_line_inner(const char *line, int depth) { str_append(expanded, " ", sizeof(expanded)); str_append(expanded, tail, sizeof(expanded)); } - return execute_line_inner(expanded, depth + 1); + + char split_buf[MAX_LINE]; + str_copy(split_buf, expanded, sizeof(split_buf)); + char *expanded_argv[MAX_ARGS]; + int expanded_argc = split_args(split_buf, expanded_argv, MAX_ARGS); + if (expanded_argc <= 0) return 0; + return execute_argv_inner(expanded_argc, expanded_argv, depth + 1, isolated, background, want_exit, out_pid); } } - int bi = execute_builtin(argc, argv); - if (bi == 2) return 2; - if (bi >= 0) return 0; + int bi = -1; + if (isolated) { + alias_t saved_aliases[MAX_ALIASES]; + int saved_alias_count = 0; + char saved_cwd[256]; + bool had_cwd = getcwd(saved_cwd, sizeof(saved_cwd)) != NULL; + + alias_snapshot_save(saved_aliases, &saved_alias_count); + bi = execute_builtin(argc, argv); + + alias_snapshot_restore(saved_aliases, saved_alias_count); + if (had_cwd) chdir(saved_cwd); + } else { + bi = execute_builtin(argc, argv); + } + + if (bi == 2) { + if (!isolated && want_exit) *want_exit = true; + return 0; + } + if (bi >= 0) return bi == 0 ? 0 : 1; char full_path[256]; int cmd_res = resolve_command(argv[0], full_path, sizeof(full_path)); @@ -1565,9 +1837,13 @@ static int execute_line_inner(const char *line, int depth) { char args_buf[256]; build_args_string(argc, argv, 1, args_buf, sizeof(args_buf)); + uint64_t spawn_flags = background + ? (SPAWN_FLAG_INHERIT_TTY | SPAWN_FLAG_BACKGROUND) + : (SPAWN_FLAG_TERMINAL | SPAWN_FLAG_INHERIT_TTY); + int pid = -1; for (int attempt = 0; attempt < 5; attempt++) { - pid = sys_spawn(full_path, args_buf[0] ? args_buf : NULL, SPAWN_FLAG_TERMINAL | SPAWN_FLAG_INHERIT_TTY, 0); + pid = sys_spawn(full_path, args_buf[0] ? args_buf : NULL, spawn_flags, 0); if (pid >= 0) break; sleep(10); } @@ -1578,14 +1854,278 @@ static int execute_line_inner(const char *line, int depth) { return 1; } + if (out_pid) *out_pid = pid; + if (background) return 0; + if (g_tty_id >= 0) sys_tty_set_fg(g_tty_id, pid); - wait_for_pid(pid); + int status = wait_for_pid(pid); if (g_tty_id >= 0) sys_tty_set_fg(g_tty_id, 0); - return 0; + return status; +} + +static int run_simple_command_with_fds( + bsh_simple_cmd_t *cmd, + int in_fd, + int out_fd, + bool isolate, + bool background, + bool *want_exit, + int *out_pid +) { + if (out_pid) *out_pid = -1; + if (!cmd || cmd->argc <= 0) return 0; + + int saved_in = sys_dup(0); + int saved_out = sys_dup(1); + + if (saved_in < 0 || saved_out < 0) { + if (saved_in >= 0) sys_close(saved_in); + if (saved_out >= 0) sys_close(saved_out); + set_color(g_color_error); + printf("bsh: redirection/pipeline is unavailable in this session\n"); + reset_color(); + return 1; + } + + int redir_in_fd = -1; + int redir_out_fd = -1; + bool in_is_kernel = false; + bool out_is_kernel = false; + int rc = 0; + + // 1. Handle Pipe Input + if (in_fd >= 0 && sys_dup2(in_fd, 0) < 0) { + set_color(g_color_error); + printf("bsh: failed to set pipeline input\n"); + reset_color(); + rc = 1; + goto done; + } + + // 2. Handle File Redirection Input (overrides pipe) + if (cmd->redir_in) { + redir_in_fd = bsh_open_file(cmd->redir_in, "r", &in_is_kernel); + if (redir_in_fd < 0 || sys_dup2(redir_in_fd, 0) < 0) { + set_color(g_color_error); + printf("bsh: cannot read from %s\n", cmd->redir_in); + reset_color(); + rc = 1; + goto done; + } + } + + // 3. Handle Pipe Output + if (out_fd >= 0 && sys_dup2(out_fd, 1) < 0) { + set_color(g_color_error); + printf("bsh: failed to set pipeline output\n"); + reset_color(); + rc = 1; + goto done; + } + + // 4. Handle File Redirection Output (overrides pipe) + if (cmd->redir_out) { + const char *mode = cmd->redir_append ? "a" : "w"; + redir_out_fd = bsh_open_file(cmd->redir_out, mode, &out_is_kernel); + if (redir_out_fd < 0 || sys_dup2(redir_out_fd, 1) < 0) { + set_color(g_color_error); + printf("bsh: cannot write to %s\n", cmd->redir_out); + reset_color(); + rc = 1; + goto done; + } + } + + rc = execute_argv_inner(cmd->argc, cmd->argv, 0, isolate, background, want_exit, out_pid); + +done: + if (redir_in_fd >= 0) sys_close(redir_in_fd); + if (redir_out_fd >= 0) sys_close(redir_out_fd); + + sys_dup2(saved_in, 0); + sys_dup2(saved_out, 1); + sys_close(saved_in); + sys_close(saved_out); + return rc; +} + +static bool parse_pipeline( + bsh_token_t toks[], + int *idx, + bsh_simple_cmd_t cmds[], + int *cmd_count +) { + if (!toks || !idx || !cmds || !cmd_count) return false; + *cmd_count = 0; + + if (!parse_simple_command(toks, idx, &cmds[*cmd_count])) return false; + (*cmd_count)++; + + while (toks[*idx].type == TOK_PIPE) { + (*idx)++; + if (*cmd_count >= MAX_PIPE_CMDS) { + print_syntax_error("pipeline too long"); + return false; + } + if (!parse_simple_command(toks, idx, &cmds[*cmd_count])) return false; + (*cmd_count)++; + } + return true; +} + +static int execute_pipeline_cmds( + bsh_simple_cmd_t cmds[], + int cmd_count, + bool background, + bool *want_exit +) { + if (!cmds || cmd_count <= 0) return 0; + + if (cmd_count == 1) { + int pid = -1; + int res = run_simple_command_with_fds(&cmds[0], -1, -1, background, background, want_exit, &pid); + return background ? 0 : res; + } + + int pipes[MAX_PIPE_CMDS - 1][2]; + int pids[MAX_PIPE_CMDS]; + for (int i = 0; i < cmd_count; i++) pids[i] = -1; + + for (int i = 0; i < cmd_count - 1; i++) { + if (pipe(pipes[i]) < 0) { + set_color(g_color_error); + printf("bsh: failed to create pipe\n"); + reset_color(); + for (int j = 0; j < i; j++) { + close(pipes[j][0]); + close(pipes[j][1]); + } + return 1; + } + } + + for (int i = 0; i < cmd_count; i++) { + int in_fd = (i == 0) ? -1 : pipes[i - 1][0]; + int out_fd = (i == cmd_count - 1) ? -1 : pipes[i][1]; + + run_simple_command_with_fds(&cmds[i], in_fd, out_fd, true, true, want_exit, &pids[i]); + + if (in_fd >= 0) close(in_fd); + if (out_fd >= 0) close(out_fd); + } + + if (background) return 0; + + int last_status = 0; + for (int i = 0; i < cmd_count; i++) { + if (pids[i] > 0) { + int s = wait_for_pid(pids[i]); + if (i == cmd_count - 1) last_status = s; + } + } + + return last_status; +} + +static bool parse_conditional_end_index(bsh_token_t toks[], int start_idx, int *out_end_idx) { + int idx = start_idx; + bsh_simple_cmd_t cmds[MAX_PIPE_CMDS]; + int cmd_count = 0; + + if (!parse_pipeline(toks, &idx, cmds, &cmd_count)) return false; + + while (toks[idx].type == TOK_ANDAND || toks[idx].type == TOK_OROR) { + idx++; + if (!parse_pipeline(toks, &idx, cmds, &cmd_count)) return false; + } + + *out_end_idx = idx; + return true; +} + +static int execute_conditional_range( + bsh_token_t toks[], + int start_idx, + int end_idx, + bool background, + bool *want_exit +) { + int idx = start_idx; + int status = 0; + bool execute_next = true; + + while (idx < end_idx) { + bsh_simple_cmd_t cmds[MAX_PIPE_CMDS]; + int cmd_count = 0; + if (!parse_pipeline(toks, &idx, cmds, &cmd_count)) return 1; + + if (execute_next) { + status = execute_pipeline_cmds(cmds, cmd_count, background, want_exit); + if (*want_exit) return 2; + } + + if (idx >= end_idx) break; + + bsh_tok_type_t op = toks[idx].type; + idx++; + if (op == TOK_ANDAND) { + execute_next = (status == 0); + } else if (op == TOK_OROR) { + execute_next = (status != 0); + } else { + print_syntax_error("unexpected conditional operator"); + return 1; + } + } + + return status; } static int execute_line(const char *line) { - return execute_line_inner(line, 0); + if (!line || !line[0]) return 0; + + char line_copy[MAX_LINE]; + str_copy(line_copy, line, sizeof(line_copy)); + trim(line_copy); + if (!line_copy[0]) return 0; + if (line_copy[0] == '#') return 0; + + bsh_token_t toks[MAX_TOKENS]; + int tcount = tokenize_line(line_copy, toks, MAX_TOKENS); + if (tcount < 0) return 1; + if (tcount == 0 || toks[0].type == TOK_END) return 0; + + int idx = 0; + int status = 0; + bool want_exit = false; + + while (toks[idx].type != TOK_END) { + if (toks[idx].type == TOK_SEMI || toks[idx].type == TOK_AMP) { + print_syntax_error("unexpected separator"); + return 1; + } + + int end_idx = idx; + if (!parse_conditional_end_index(toks, idx, &end_idx)) return 1; + + bool background = false; + if (toks[end_idx].type == TOK_AMP) background = true; + + status = execute_conditional_range(toks, idx, end_idx, background, &want_exit); + if (want_exit) return 2; + + idx = end_idx; + if (toks[idx].type == TOK_SEMI || toks[idx].type == TOK_AMP) { + idx++; + continue; + } + if (toks[idx].type != TOK_END) { + print_syntax_error("unexpected token after command list"); + return 1; + } + } + + return status; } static bool run_script(const char *path) { diff --git a/src/userland/cli/find.c b/src/userland/cli/find.c new file mode 100644 index 0000000..cae8dee --- /dev/null +++ b/src/userland/cli/find.c @@ -0,0 +1,93 @@ +#include +#include +#include "syscall.h" + +int type = 0; // 0 is nothing, 1 is file, 2 is dir +char *name = NULL; +char *dir = "."; // random char bc it needs to have anything in it + +// match exact name +int match(const char *a, const char *b) { + return strcmp(a, b) == 0; +} + +// check if its a file with fopen +int isfile(const char *path) { + FILE *fp = fopen(path, "r"); + if (fp) { + fclose(fp); + return 1; } + return 0; +} + +// recursive find +void find(const char *path) { + FAT32_FileInfo ents[128]; + int n = sys_list(path, ents, 128); + if (n < 0) return; + + for (int i = 0; i < n; i++) { + const char *filename = ents[i].name; + if (match(filename, ".") || match(filename, "..")) { + continue; + } + char full[512]; + strcpy(full, path); + strcat(full, "/"); + strcat(full, filename); + + int f = isfile(full); + if (match(filename, name)) { + if (type == 0 || (type == 1 && f) || (type == 2 && !f)) { + printf("%s\n", full); + } + } + if (!f) { + find(full); + } + } +} + +int main(int argc, char *argv[]) { + // check if user needs help + if (argc > 1 && strcmp(argv[1], "--help") == 0) { + printf("Usage: find [DIR] [-name] [pattern] [-type] [d/f]\n"); + printf("Options:\n"); + printf("-type d: only read dir\n"); + printf("-type f: only read files\n"); + return 0; + } + // check for flags + for (int i = 1; i < argc; i++) { + if (argv[i][0] == '-') { + if (match(argv[i], "-name") && i+1 < argc) { + name = argv[++i]; + } + else if (match(argv[i], "-type") && i+1 < argc) { + i++; + if (match(argv[i], "f")) { + type = 1; + } + else if (match(argv[i], "d")) { + type = 2; + } + else { + printf("-only type f or d\n"); + return 1; + } + } + } + // if dir hasnt been set yet + else if (dir[0] == '.') { + dir = argv[i]; + } + } + + if (!name) { + printf("Wrong usage, --help for more info\n"); + return 1; + } + + find(dir); + return 0; +} diff --git a/src/userland/cli/grep.c b/src/userland/cli/grep.c new file mode 100644 index 0000000..8b265f0 --- /dev/null +++ b/src/userland/cli/grep.c @@ -0,0 +1,262 @@ +// Copyright (c) 2026 maro (whitehai11) +// This software is released under the GNU General Public License v3.0. See LICENSE file for details. +// This header needs to maintain in any file it is present in, as per the GPL license terms. +// BOREDOS_APP_DESC: Search for text inside a file. + +#include "../libc/syscall.h" +#include "../libc/stdlib.h" +#include "../libc/string.h" +#include "../libc/stdio.h" + +#define READ_BUF_SIZE 4096 +#define LINE_BUF_SIZE 1024 +#define MAX_PATH 512 +#define MAX_ENTRIES 256 + +// Flags +static int g_show_numbers = 0; +static int g_ignore_case = 0; +static int g_count_only = 0; +static int g_invert = 0; // -v +static int g_files_only = 0; // -l +static int g_word_match = 0; // -w +static int g_line_match = 0; // -x +static int g_recursive = 0; // -r / -R +static int g_multi_file = 0; // more than one file → prefix output with filename + +static const char *g_pattern = NULL; + +// Total match count across all files (used for -c with -r) +static int g_total_matches = 0; + +static int sc_strcmp(const char *a, const char *b) { + while (*a && *a == *b) { a++; b++; } + return (unsigned char)*a - (unsigned char)*b; +} + +static void print_usage(void) { + printf("Usage: grep [options] [file...]\n"); + printf("\n"); + printf("Search for text inside files.\n"); + printf("\n"); + printf("Options:\n"); + printf(" -n Show line numbers\n"); + printf(" -i Case-insensitive search\n"); + printf(" -c Print match count only\n"); + printf(" -v Invert match (print non-matching lines)\n"); + printf(" -l Print only filenames with matches\n"); + printf(" -w Match whole words only\n"); + printf(" -x Match whole lines only\n"); + printf(" -r, -R Recursive search in directories\n"); + printf(" -h Show this help\n"); +} + +static char to_lower(char c) { + if (c >= 'A' && c <= 'Z') return c + 32; + return c; +} + +static int is_word_char(char c) { + return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || + (c >= '0' && c <= '9') || c == '_'; +} + +/* Check if needle appears in haystack at position i as a whole word */ +static int match_at(const char *haystack, int i, int h_len, + const char *needle, int n_len, int ignore_case) { + for (int j = 0; j < n_len; j++) { + char h = ignore_case ? to_lower(haystack[i + j]) : haystack[i + j]; + char n = ignore_case ? to_lower(needle[j]) : needle[j]; + if (h != n) return 0; + } + if (g_word_match) { + if (i > 0 && is_word_char(haystack[i - 1])) return 0; + if (i + n_len < h_len && is_word_char(haystack[i + n_len])) return 0; + } + return 1; +} + +static int str_contains(const char *haystack, const char *needle, int ignore_case) { + int h_len = (int)strlen(haystack); + int n_len = (int)strlen(needle); + + if (n_len == 0) return 1; + if (n_len > h_len) return 0; + + for (int i = 0; i <= h_len - n_len; i++) { + if (match_at(haystack, i, h_len, needle, n_len, ignore_case)) + return 1; + } + return 0; +} + +static int line_matches(const char *line) { + if (g_line_match) { + /* Whole line must equal pattern */ + int h_len = (int)strlen(line); + int n_len = (int)strlen(g_pattern); + if (h_len != n_len) return 0; + return match_at(line, 0, h_len, g_pattern, n_len, g_ignore_case); + } + return str_contains(line, g_pattern, g_ignore_case); +} + +/* Grep a single open file descriptor, printing results prefixed by filename if needed */ +static int grep_fd(int fd, const char *filename) { + static char read_buf[READ_BUF_SIZE]; + static char line[LINE_BUF_SIZE]; + int line_pos = 0; + int line_num = 0; + int match_cnt = 0; + + /* Helper: process one complete line */ + #define PROCESS_LINE() do { \ + line[line_pos] = '\0'; \ + line_num++; \ + int matched = line_matches(line); \ + if (g_invert) matched = !matched; \ + if (matched) { \ + match_cnt++; \ + if (!g_count_only && !g_files_only) { \ + if (g_multi_file) printf("%s:", filename); \ + if (g_show_numbers) printf("%d:", line_num); \ + printf("%s\n", line); \ + } \ + } \ + line_pos = 0; \ + } while (0) + + while (1) { + int bytes = sys_read(fd, read_buf, READ_BUF_SIZE); + if (bytes <= 0) break; + + for (int i = 0; i < bytes; i++) { + char c = read_buf[i]; + if (c == '\n' || line_pos >= LINE_BUF_SIZE - 1) { + PROCESS_LINE(); + } else if (c != '\r') { + line[line_pos++] = c; + } + } + } + if (line_pos > 0) PROCESS_LINE(); + + #undef PROCESS_LINE + + if (g_count_only) + printf("%s%d\n", g_multi_file ? filename : "", match_cnt > 0 ? match_cnt : 0); + + if (g_files_only && match_cnt > 0) + printf("%s\n", filename); + + g_total_matches += match_cnt; + return match_cnt; +} + +static int grep_file(const char *path) { + int fd = sys_open(path, "r"); + if (fd < 0) { + printf("grep: cannot open '%s'\n", path); + return 0; + } + int n = grep_fd(fd, path); + sys_close(fd); + return n; +} + +static void grep_recursive(const char *path) { + FAT32_FileInfo info; + if (sys_get_file_info(path, &info) < 0) { + printf("grep: cannot access '%s'\n", path); + return; + } + + if (!info.is_directory) { + grep_file(path); + return; + } + + FAT32_FileInfo entries[MAX_ENTRIES]; + int count = sys_list(path, entries, MAX_ENTRIES); + if (count < 0) return; + + for (int i = 0; i < count; i++) { + const char *name = entries[i].name; + if (name[0] == '.' && (name[1] == '\0' || (name[1] == '.' && name[2] == '\0'))) + continue; + + char full[MAX_PATH]; + int plen = (int)strlen(path); + int nlen = (int)strlen(name); + if (plen + 1 + nlen + 1 > MAX_PATH) continue; + + int slash = (plen == 1 && path[0] == '/') ? 0 : 1; + for (int j = 0; j < plen; j++) full[j] = path[j]; + if (slash) full[plen] = '/'; + for (int j = 0; j <= nlen; j++) full[plen + slash + j] = name[j]; + + if (entries[i].is_directory) + grep_recursive(full); + else + grep_file(full); + } +} + +int main(int argc, char **argv) { + int arg_offset = 1; + + for (int i = 1; i < argc; i++) { + if (argv[i][0] != '-') break; + if (sc_strcmp(argv[i], "-n") == 0) { + g_show_numbers = 1; arg_offset++; + } else if (sc_strcmp(argv[i], "-i") == 0) { + g_ignore_case = 1; arg_offset++; + } else if (sc_strcmp(argv[i], "-c") == 0) { + g_count_only = 1; arg_offset++; + } else if (sc_strcmp(argv[i], "-v") == 0) { + g_invert = 1; arg_offset++; + } else if (sc_strcmp(argv[i], "-l") == 0) { + g_files_only = 1; arg_offset++; + } else if (sc_strcmp(argv[i], "-w") == 0) { + g_word_match = 1; arg_offset++; + } else if (sc_strcmp(argv[i], "-x") == 0) { + g_line_match = 1; arg_offset++; + } else if (sc_strcmp(argv[i], "-r") == 0 || + sc_strcmp(argv[i], "-R") == 0) { + g_recursive = 1; arg_offset++; + } else if (sc_strcmp(argv[i], "-h") == 0 || + sc_strcmp(argv[i], "--help") == 0) { + print_usage(); + return 0; + } else { + printf("grep: unknown option '%s'\n", argv[i]); + return 1; + } + } + + if (argc - arg_offset < 1) { + print_usage(); + return 1; + } + + g_pattern = argv[arg_offset]; + + // Need at least a path when not reading stdin + if (argc - arg_offset < 2) { + print_usage(); + return 1; + } + + // Multiple files → prefix output with filename + g_multi_file = (argc - arg_offset > 2) || g_recursive; + + for (int i = arg_offset + 1; i < argc; i++) { + if (g_recursive) { + grep_recursive(argv[i]); + } else { + grep_file(argv[i]); + } + } + + return g_total_matches > 0 ? 0 : 1; +} diff --git a/src/userland/cli/sysfetch.c b/src/userland/cli/sysfetch.c index 8bc07f2..ec953b3 100644 --- a/src/userland/cli/sysfetch.c +++ b/src/userland/cli/sysfetch.c @@ -323,13 +323,22 @@ int main(int argc, char **argv) { int b = sys_read(fd_u, u_buf, 63); u_buf[b] = 0; sys_close(fd_u); - int sec = atoi(u_buf); - int mins = sec / 60; + int sec = atoi(u_buf); + int days = sec / 86400; + int hrs = (sec % 86400) / 3600; + int mins = (sec % 3600) / 60; strcpy(info_lines[info_line_count], config.uptime_label); strcat(info_lines[info_line_count], ": "); - itoa(mins, temp_buf); - strcat(info_lines[info_line_count], temp_buf); - strcat(info_lines[info_line_count++], " mins"); + if (days > 0) { + itoa(days, temp_buf); strcat(info_lines[info_line_count], temp_buf); + strcat(info_lines[info_line_count], days == 1 ? " day, " : " days, "); + } + if (hrs > 0 || days > 0) { + itoa(hrs, temp_buf); strcat(info_lines[info_line_count], temp_buf); + strcat(info_lines[info_line_count], hrs == 1 ? " hour, " : " hours, "); + } + itoa(mins, temp_buf); strcat(info_lines[info_line_count], temp_buf); + strcat(info_lines[info_line_count++], mins == 1 ? " min" : " mins"); } } if (config.cpu_label[0]) { diff --git a/src/userland/gui/terminal.c b/src/userland/gui/terminal.c index 8dd052f..37a3349 100644 --- a/src/userland/gui/terminal.c +++ b/src/userland/gui/terminal.c @@ -1235,10 +1235,22 @@ int main(void) { for (int i = 0; i < g_tab_count; i++) { TerminalSession *s = &g_tabs[i]; int read = 0; + + // Read any remaining output from the TTY while ((read = sys_tty_read_out(s->tty_id, out_buf, sizeof(out_buf))) > 0) { session_process_output(s, out_buf, read); if (i == g_active_tab) dirty = true; } + + /* Check if the shell process has exited + if it has, close the tab.*/ + int status = 0; + if (sys_waitpid(s->bsh_pid, &status, 1) > 0) { + close_tab(i); + i--; + dirty = true; + continue; + } } while (ui_get_event(g_win, &ev)) { diff --git a/src/userland/libc/posix_io.c b/src/userland/libc/posix_io.c index 320b8d3..3c52550 100644 --- a/src/userland/libc/posix_io.c +++ b/src/userland/libc/posix_io.c @@ -53,6 +53,7 @@ typedef struct { int refcount; int flags; int kernel_fd; + int owns_kernel_fd; pipe_state_t *pipe; } fd_handle_t; @@ -60,6 +61,10 @@ static fd_handle_t *g_fd_table[POSIX_MAX_FDS]; static fd_handle_t g_stdio_handles[3]; static int g_fd_initialized = 0; +static int _b_is_stdio_handle(const fd_handle_t *h) { + return (h >= &g_stdio_handles[0] && h <= &g_stdio_handles[2]); +} + static void _b_fd_init(void) { int i; if (g_fd_initialized) { @@ -73,6 +78,7 @@ static void _b_fd_init(void) { g_stdio_handles[i].refcount = 1; g_stdio_handles[i].flags = O_RDWR; g_stdio_handles[i].kernel_fd = i; + g_stdio_handles[i].owns_kernel_fd = 0; g_stdio_handles[i].pipe = NULL; g_fd_table[i] = &g_stdio_handles[i]; } @@ -274,6 +280,7 @@ __attribute__((weak)) int open(const char *pathname, int flags, ...) { h->refcount = 1; h->flags = flags; h->kernel_fd = kfd; + h->owns_kernel_fd = 1; h->pipe = NULL; g_fd_table[fd] = h; @@ -300,7 +307,7 @@ __attribute__((weak)) int close(int fd) { } if (h->type == HANDLE_KERNEL_FD) { - if (h->kernel_fd >= 3) { + if (h->owns_kernel_fd) { sys_close(h->kernel_fd); } } else if (h->type == HANDLE_PIPE_READ || h->type == HANDLE_PIPE_WRITE) { @@ -384,10 +391,13 @@ __attribute__((weak)) ssize_t write(int fd, const void *buf, size_t count) { return (ssize_t)n; } - if (h->kernel_fd <= 2) { + if (_b_is_stdio_handle(h)) { n = sys_write(h->kernel_fd, (const char *)buf, (int)count); } else { n = sys_write_fs(h->kernel_fd, buf, (uint32_t)count); + if (n < 0) { + n = sys_write(h->kernel_fd, (const char *)buf, (int)count); + } } if (n < 0) { @@ -442,7 +452,7 @@ __attribute__((weak)) int isatty(int fd) { errno = EBADF; return 0; } - return (h->type == HANDLE_KERNEL_FD && h->kernel_fd <= 2) ? 1 : 0; + return (h->type == HANDLE_KERNEL_FD && _b_is_stdio_handle(h)) ? 1 : 0; } __attribute__((weak)) int fstat(int fd, struct stat *statbuf) { @@ -471,31 +481,38 @@ __attribute__((weak)) int fstat(int fd, struct stat *statbuf) { __attribute__((weak)) int dup(int oldfd) { fd_handle_t *h; + fd_handle_t *src; int newfd; int newkfd; _b_fd_init(); - h = _b_get_handle(oldfd); - if (!h) { + src = _b_get_handle(oldfd); + if (!src) { errno = EBADF; return -1; } - if (h->type != HANDLE_KERNEL_FD) { + if (src->type != HANDLE_KERNEL_FD) { errno = ENOTSUP; return -1; } - newkfd = sys_dup(h->kernel_fd); + newkfd = sys_dup(src->kernel_fd); if (newkfd < 0) { - errno = EBADF; - return -1; + if (_b_is_stdio_handle(src)) { + newkfd = src->kernel_fd; + } else { + errno = EBADF; + return -1; + } } newfd = _b_alloc_fd_from(0); if (newfd < 0) { - sys_close(newkfd); - errno = EBUSY; + if (newkfd >= 3) { + sys_close(newkfd); + } + errno = EBADF; return -1; } @@ -510,19 +527,20 @@ __attribute__((weak)) int dup(int oldfd) { h->refcount = 1; h->flags = O_RDWR; h->kernel_fd = newkfd; + h->owns_kernel_fd = (newkfd != src->kernel_fd) ? 1 : 0; h->pipe = NULL; g_fd_table[newfd] = h; return newfd; } __attribute__((weak)) int dup2(int oldfd, int newfd) { - fd_handle_t *h; + fd_handle_t *src; fd_handle_t *nh; - int newkfd; + int kfd_res; _b_fd_init(); - h = _b_get_handle(oldfd); - if (!h || newfd < 0 || newfd >= POSIX_MAX_FDS) { + src = _b_get_handle(oldfd); + if (!src || newfd < 0 || newfd >= POSIX_MAX_FDS) { errno = EBADF; return -1; } @@ -531,36 +549,48 @@ __attribute__((weak)) int dup2(int oldfd, int newfd) { return newfd; } - if (h->type != HANDLE_KERNEL_FD) { + if (src->type != HANDLE_KERNEL_FD) { errno = ENOTSUP; return -1; } - if (g_fd_table[newfd]) { - if (close(newfd) != 0) { - return -1; - } - } - - newkfd = sys_dup(h->kernel_fd); - if (newkfd < 0) { + // Force kernel to update its FD table for the new slot + kfd_res = sys_dup2(src->kernel_fd, newfd); + if (kfd_res < 0) { errno = EBADF; return -1; } - nh = (fd_handle_t *)malloc(sizeof(fd_handle_t)); - if (!nh) { - errno = ENOMEM; - return -1; + // If newfd was already open in libc, we need to replace its handle + if (g_fd_table[newfd] && !_b_is_stdio_handle(g_fd_table[newfd])) { + close(newfd); } - nh->type = HANDLE_KERNEL_FD; - nh->refcount = 1; - nh->flags = h->flags; - nh->kernel_fd = newkfd; - nh->pipe = NULL; + // If it's a stdio handle, we update it in place + if (newfd >= 0 && newfd <= 2) { + nh = &g_stdio_handles[newfd]; + nh->type = HANDLE_KERNEL_FD; + nh->kernel_fd = newfd; + nh->flags = src->flags; + nh->refcount = 1; + nh->owns_kernel_fd = 0; + nh->pipe = NULL; + g_fd_table[newfd] = nh; + } else { + nh = (fd_handle_t *)malloc(sizeof(fd_handle_t)); + if (!nh) { + errno = ENOMEM; + return -1; + } + nh->type = HANDLE_KERNEL_FD; + nh->refcount = 1; + nh->flags = src->flags; + nh->kernel_fd = newfd; + nh->owns_kernel_fd = 1; + nh->pipe = NULL; + g_fd_table[newfd] = nh; + } - g_fd_table[newfd] = nh; return newfd; }