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
-
-
-

-
-
A modern x86_64 hobbyist operating system built from the ground up.
-
- [](https://www.gnu.org/licenses/gpl-3.0)
- 
- 
- 
-
-
-
- [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)
-
-
-
----
-
-
-
-> [!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
-
-
-
----
-
-## ☕ Support the Journey
-
-If you find BoredOS interesting or useful, consider fueling development with 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;
}