Repository: clstatham/k4dos Branch: master Commit: e8aab3ba0914 Files: 91 Total size: 431.2 KB Directory structure: gitextract_c29w2sxr/ ├── .cargo/ │ ├── config.toml │ ├── debug.toml │ ├── linker.ld │ ├── release.toml │ ├── runner_debug.sh │ ├── runner_release.sh │ └── x86_64-kados.json ├── .gitignore ├── .vscode/ │ ├── launch.json │ └── settings.json ├── Cargo.toml ├── LICENSE ├── README.md ├── build.rs ├── conf/ │ ├── grub.cfg │ └── limine.cfg ├── deps.sh ├── kados.config ├── rust-toolchain.toml ├── src/ │ ├── arch/ │ │ ├── mod.rs │ │ └── x86_64/ │ │ ├── cpu_local.rs │ │ ├── gdt.rs │ │ ├── idt.rs │ │ ├── mod.rs │ │ ├── syscall.rs │ │ ├── task.rs │ │ └── time.rs │ ├── backtrace.rs │ ├── fs/ │ │ ├── devfs/ │ │ │ ├── fb.rs │ │ │ ├── input.rs │ │ │ ├── mod.rs │ │ │ ├── null.rs │ │ │ ├── socket.rs │ │ │ ├── tty.rs │ │ │ └── urandom.rs │ │ ├── initramfs/ │ │ │ ├── dir.rs │ │ │ ├── file.rs │ │ │ ├── mod.rs │ │ │ ├── root.rs │ │ │ └── symlink.rs │ │ ├── mod.rs │ │ ├── opened_file.rs │ │ ├── path.rs │ │ └── pipe.rs │ ├── god_mode.rs │ ├── graphics/ │ │ └── mod.rs │ ├── logging.rs │ ├── main.rs │ ├── mem/ │ │ ├── addr.rs │ │ ├── addr_space.rs │ │ ├── allocator.rs │ │ ├── consts.rs │ │ ├── mod.rs │ │ └── paging/ │ │ ├── mapper.rs │ │ ├── mod.rs │ │ ├── table.rs │ │ └── units.rs │ ├── serial.rs │ ├── task/ │ │ ├── group.rs │ │ ├── mod.rs │ │ ├── scheduler.rs │ │ ├── signal.rs │ │ ├── vmem.rs │ │ └── wait_queue.rs │ ├── userland/ │ │ ├── buffer.rs │ │ ├── elf.rs │ │ ├── mod.rs │ │ └── syscall/ │ │ ├── mod.rs │ │ └── syscall_impl/ │ │ ├── fs.rs │ │ ├── mem.rs │ │ ├── mod.rs │ │ ├── signal.rs │ │ ├── sys.rs │ │ ├── task.rs │ │ └── time.rs │ ├── util/ │ │ ├── ctypes.rs │ │ ├── errno.rs │ │ ├── error.rs │ │ ├── lock.rs │ │ ├── mod.rs │ │ ├── ringbuffer.rs │ │ └── stack.rs │ └── vga_text.rs └── userland/ ├── .gitignore ├── .vscode/ │ └── settings.json ├── Cargo.toml ├── kados_syscall/ │ ├── .cargo/ │ │ └── config.toml │ ├── Cargo.toml │ └── src/ │ ├── consts.rs │ └── lib.rs └── rust-toolchain.toml ================================================ FILE CONTENTS ================================================ ================================================ FILE: .cargo/config.toml ================================================ [build] target = "x86_64-unknown-none" rustflags = ["-Cforce-frame-pointers=yes"] [unstable] build-std = ["core", "compiler_builtins", "alloc"] build-std-features = ["compiler-builtins-mem"] [alias] qemu = "run --release --config .cargo/release.toml" qemu-debug = "run --config .cargo/debug.toml" ================================================ FILE: .cargo/debug.toml ================================================ [target.x86_64-unknown-none] runner = ["bash", ".cargo/runner_debug.sh"] ================================================ FILE: .cargo/linker.ld ================================================ ENTRY(start) OUTPUT_ARCH(i386:x86-64) OUTPUT_FORMAT(elf64-x86-64) KERNEL_BASE = 0xffffffff80000000; SECTIONS { . = KERNEL_BASE + SIZEOF_HEADERS; .hash : { *(.hash) } .gnu.hash : { *(.gnu.hash) } .dynsym : { *(.dynsym) } .dynstr : { *(.dynstr) } .rela : { *(.rela*) } .rodata : { *(.rodata .rodata.*) } .note.gnu.build-id : { *(.note.gnu.build-id) } .eh_frame_hdr : { PROVIDE(__eh_frame_hdr = .); KEEP(*(.eh_frame_hdr)) PROVIDE(__eh_frame_hdr_end = .); } .eh_frame : { PROVIDE(__eh_frame = .); KEEP(*(.eh_frame)) PROVIDE(__eh_frame_end = .); } .gcc_except_table : { KEEP(*(.gcc_except_table .gcc_except_table.*)) } . += CONSTANT(MAXPAGESIZE); .plt : { *(.plt .plt.*) } .text : { *(.text .text.*) } . += CONSTANT(MAXPAGESIZE); .tdata : { *(.tdata .tdata.*) } .tbss : { *(.tbss .tbss.*) } .data.rel.ro : { *(.data.rel.ro .data.rel.ro.*) } .dynamic : { *(.dynamic) } . = DATA_SEGMENT_RELRO_END(0, .); .got : { *(.got .got.*) } .got.plt : { *(.got.plt .got.plt.*) } .data : { *(.data .data.*) } .bss : { *(.bss .bss.*) *(COMMON) } . = DATA_SEGMENT_END(.); .comment 0 : { *(.comment) } .debug 0 : { *(.debug) } .debug_abbrev 0 : { *(.debug_abbrev) } .debug_aranges 0 : { *(.debug_aranges) } .debug_frame 0 : { *(.debug_frame) } .debug_funcnames 0 : { *(.debug_funcnames) } .debug_info 0 : { *(.debug_info .gnu.linkonce.wi.*) } .debug_line 0 : { *(.debug_line) } .debug_loc 0 : { *(.debug_loc) } .debug_macinfo 0 : { *(.debug_macinfo) } .debug_pubnames 0 : { *(.debug_pubnames) } .debug_pubtypes 0 : { *(.debug_pubtypes) } .debug_ranges 0 : { *(.debug_ranges) } .debug_sfnames 0 : { *(.debug_sfnames) } .debug_srcinfo 0 : { *(.debug_srcinfo) } .debug_str 0 : { *(.debug_str) } .debug_typenames 0 : { *(.debug_typenames) } .debug_varnames 0 : { *(.debug_varnames) } .debug_weaknames 0 : { *(.debug_weaknames) } .line 0 : { *(.line) } .shstrtab 0 : { *(.shstrtab) } .strtab 0 : { *(.strtab) } .symtab 0 : { *(.symtab) } } ================================================ FILE: .cargo/release.toml ================================================ [target.x86_64-unknown-none] runner = ["bash", ".cargo/runner_release.sh"] ================================================ FILE: .cargo/runner_debug.sh ================================================ #! /bin/bash # # This script will be executed by `cargo run`. set -xe LIMINE_GIT_URL="https://github.com/limine-bootloader/limine.git" # Cargo passes the path to the built executable as the first argument. KERNEL=$1 # Clone the `limine` repository if we don't have it yet. if [ ! -d target/limine ]; then git clone $LIMINE_GIT_URL --depth=1 --branch v3.0-branch-binary target/limine fi # Make sure we have an up-to-date version of the bootloader. cd target/limine git fetch make cd - # Copy the needed files into an ISO image. mkdir -p target/iso_root cp $KERNEL conf/limine.cfg target/limine/limine{.sys,-cd.bin,-cd-efi.bin} target/iso_root xorriso -as mkisofs \ -b limine-cd.bin \ -no-emul-boot -boot-load-size 4 -boot-info-table \ --efi-boot limine-cd-efi.bin \ -efi-boot-part --efi-boot-image --protective-msdos-label \ target/iso_root -o $KERNEL.iso # For the image to be bootable on BIOS systems, we must run `limine-deploy` on it. target/limine/limine-deploy $KERNEL.iso # Run the created image with QEMU. # -machine q35 -cpu EPYC echo "Running in debug mode." >&2 qemu-system-x86_64 \ -machine q35 -cpu EPYC -M smm=off \ -D target/log.txt -d int,guest_errors -no-reboot -no-shutdown \ -s -S \ -serial stdio \ -serial file:target/fb_log.txt \ -m 4G \ -cdrom $KERNEL.iso >&2 ================================================ FILE: .cargo/runner_release.sh ================================================ #! /bin/bash # # This script will be executed by `cargo run`. set -xe LIMINE_GIT_URL="https://github.com/limine-bootloader/limine.git" # Cargo passes the path to the built executable as the first argument. KERNEL=$1 # Clone the `limine` repository if we don't have it yet. if [ ! -d target/limine ]; then git clone $LIMINE_GIT_URL --depth=1 --branch v3.0-branch-binary target/limine fi # Make sure we have an up-to-date version of the bootloader. cd target/limine git fetch make cd - # Copy the needed files into an ISO image. mkdir -p target/iso_root cp $KERNEL conf/limine.cfg target/limine/limine{.sys,-cd.bin,-cd-efi.bin} target/iso_root xorriso -as mkisofs \ -b limine-cd.bin \ -no-emul-boot -boot-load-size 4 -boot-info-table \ --efi-boot limine-cd-efi.bin \ -efi-boot-part --efi-boot-image --protective-msdos-label \ target/iso_root -o $KERNEL.iso # For the image to be bootable on BIOS systems, we must run `limine-deploy` on it. target/limine/limine-deploy $KERNEL.iso # Run the created image with QEMU. # -machine q35 -cpu EPYC echo "Running in release mode." >&2 qemu-system-x86_64 \ -M smm=off \ -machine q35 -cpu EPYC \ -D target/log.txt -d int,guest_errors -no-reboot -no-shutdown \ -s \ -serial stdio \ -serial file:target/fb_log.txt \ -m 4G \ -cdrom $KERNEL.iso >&2 ================================================ FILE: .cargo/x86_64-kados.json ================================================ { "llvm-target": "x86_64-unknown-none-elf", "target-endian": "little", "target-pointer-width": "64", "target-c-int-width": "32", "data-layout": "e-m:e-p270:32:32-p271:32:32-p272:64:64-i64:64-f80:128-n8:16:32:64-S128", "arch": "x86_64", "os": "none", "env": "", "vendor": "unknown", "linker": "rust-lld", "linker-flavor": "ld.lld", "pre-link-args": { "ld.lld": [ "--script=.cargo/linker.ld" ] }, "features": "-mmx,-sse,+soft-float", "executables": true, "relocation-model": "pic", "code-model": "kernel", "disable-redzone": true, "frame-pointer": "always", "no-default-libraries": true, "position-independent-executables": false, "tls-model": "global-dynamic" } ================================================ FILE: .gitignore ================================================ /target /initramfs /initramfs_old /extern *.objdump *.map strace_vi.txt kados.map /extern_old ================================================ FILE: .vscode/launch.json ================================================ { "version": "0.2.0", "configurations": [ { "type": "lldb", "request": "custom", "name": "Attach to gdbserver", "initCommands": [ "platform select remote-gdb-server" ], "targetCreateCommands": [ "target create ${workspaceFolder}/target/iso_root/k4dos" ], "processCreateCommands": [ "gdb-remote 127.0.0.1:1234" ] }, ] } ================================================ FILE: .vscode/settings.json ================================================ { "lldb.displayFormat": "auto", "lldb.dereferencePointers": true, "lldb.consoleMode": "commands", "lldb.showDisassembly": "auto", "rust-analyzer.check.command": "clippy", "editor.formatOnSave": true, "rust-analyzer.cargo.target": "x86_64-unknown-none", } ================================================ FILE: Cargo.toml ================================================ [package] name = "k4dos" version = "0.1.0" edition = "2024" [[bin]] name = "k4dos" test = false bench = false # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] limine = "0.3.1" volatile = "0.2.6" # DO NOT CHANGE spin = "0.9.8" bit_field = "0.10.2" x86 = "0.52.0" x86_64 = "0.15.2" log = "0.4" pic8259 = "0.11" bitflags = "2.6.0" uart_16550 = "0.3.2" crossbeam-utils = { version = "0.8.20", default-features = false } atomic_refcell = "0.1.13" buddy_system_allocator = { version = "0.11.0", features = [] } pc-keyboard = "0.8.0" xmas-elf = "0.8" rustc-demangle = "0.1.24" arrayvec = { version = "0.7.6", default-features = false } x2apic = "0.4.3" elfloader = "0.16.0" bitvec = { version = "1.0.1", default-features = false } embedded-graphics = "0.8.1" smoltcp = { version = "0.12.0", default-features = false, features = [ "alloc", "proto-ipv4", "socket", "socket-raw", "socket-udp", "socket-tcp", "proto-dhcpv4", "medium-ethernet", ] } lazy_static = { version = "1.5.0", features = ["spin_no_std"] } embedded-profiling = { version = "0.3.0", features = ["proc-macros"] } [profile.release] debug = 1 ================================================ FILE: LICENSE ================================================ MIT License Copyright (c) 2022 Connor Statham Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ================================================ FILE: README.md ================================================ # k4dos K4DOS is a hobby operating system written in Rust. It current supports x86_64 architecture and aims for Linux ABI compatibility (running unmodified Linux programs). ## k4dos running NetHack ![image](https://user-images.githubusercontent.com/58794204/227795203-976c46ac-1c75-4125-89d8-4377397deff0.png) ## k4dos running FreeDOOM ![image](https://user-images.githubusercontent.com/58794204/227990608-a0dec362-8624-4869-84ea-9db5dd170a14.png) ## k4dos running [RustyRays](https://github.com/clstatham/rustyrays/tree/k4dos_port), my own ray tracer ![image](https://user-images.githubusercontent.com/58794204/228302199-c4a6e4ea-590d-4453-8987-1374773342b3.png) ## Building I haven't set up a proper build system for this to work on other people's machines yet. Contact me or open a PR if you'd like to help with that. For now, this repo exists to present the source code to others who might want to learn from it. ## License MIT licensed (see LICENSE file) ================================================ FILE: build.rs ================================================ use std::{env, error::Error}; fn main() -> Result<(), Box> { // Get the name of the package. let kernel_name = env::var("CARGO_PKG_NAME")?; // let status = Command::new("./deps.sh") // .args(["makeimg"]) // .status() // .unwrap(); // assert!(status.success()); println!("cargo:rustc-link-arg-bin={kernel_name}=--script=.cargo/linker.ld"); // Add linker args. println!("cargo:rustc-link-arg-bin={kernel_name}=--gc-sections"); // Have cargo rerun this script if the linker script or CARGO_PKG_ENV changes. println!("cargo:rerun-if-changed=.cargo/linker.ld"); println!("cargo:rerun-if-env-changed=CARGO_PKG_NAME"); println!("cargo:rerun-if-changed=build.rs"); Ok(()) } ================================================ FILE: conf/grub.cfg ================================================ set timeout=0 set default=0 GRUB_TERMINAL=console menuentry "K4DOS" { multiboot2 /boot/k4dos boot } ================================================ FILE: conf/limine.cfg ================================================ TIMEOUT=0 VERBOSE=yes : k4dos PROTOCOL=limine KASLR=no RESOLUTION=640x400x32 KERNEL_PATH=boot:///k4dos ================================================ FILE: deps.sh ================================================ #!/bin/bash set -e if [[ -z "$@" ]]; then echo "Usage: deps.sh [COMMANDS]..." echo "Available commands:" echo "download Downloads Busybox" echo "menuconfig Configures Busybox with menuconfig" echo "allnoconfig Configures Busybox with allnoconfig" echo "defconfig Configures Busybox with its default config (currently doesn't build!)" echo "clean Cleans the output directory" echo "build Builds Busybox" echo "makeimg Creates the initramfs image for K4DOS" echo "kash Builds Kash, the K4DOS shell" exit fi STARTDIR=$(pwd) mkdir -p extern cd extern if [[ $@ =~ "download" ]]; then echo echo "!!!IMPORTANT!!!" echo "If you just want a prebuilt image, go to the following link to download one and put it in a new folder in this directory called initramfs" echo "The prebuilt image currently contains preconfigured and prebuilt distributions of Busybox, FreeDOOM, NetHack, and RustyRays." echo "Busybox, FreeDOOM, Doom-Generic, and NetHack are subject to their own copyright and license terms." echo "https://drive.google.com/file/d/1Yl8Ei1toRCmoHbNVxOxamXrGS_s4xvf6/view" echo "Press ENTER to continue, or Ctrl-C to cancel." read echo "Downloading Busybox." git clone git://busybox.net/busybox.git echo "Installing musl with pacman and creating symlinks. (will sudo)" sudo pacman -S musl sudo ln -s /usr/bin/ar /usr/bin/musl-ar sudo ln -s /usr/bin/strip /usr/bin/musl-strip echo "Now run 'deps.sh menuconfig' and load kados.config, located in the same directory as this script" fi BUSYBOXDIR=$STARTDIR/extern/busybox cd busybox if [[ $@ =~ "menuconfig" ]]; then make menuconfig fi if [[ $@ =~ "defconfig" ]]; then make defconfig fi if [[ $@ =~ "allnoconfig" ]]; then make allnoconfig fi if [[ $@ =~ "clean" ]]; then make clean fi if [[ $@ =~ "build" ]]; then time make -j6 make install fi if [[ $@ =~ "kash" ]]; then cd $STARTDIR/userland/kash cargo build cd - fi if [[ $@ =~ "makeimg" ]]; then cd $STARTDIR mkdir -p initramfs/busybox_fs cd $STARTDIR cd initramfs/busybox_fs rm -f ../initramfs_busybox cp -r $BUSYBOXDIR/_install/* ./ cp -r $BUSYBOXDIR/busybox_unstripped ./bin/busybox mkdir -p dev find . | cpio -ov --format=newc > ../initramfs fi cd $STARTDIR echo "Done." ================================================ FILE: kados.config ================================================ # # Automatically generated make config: don't edit # Busybox version: 1.37.0.git # Sun Mar 12 20:07:49 2023 # CONFIG_HAVE_DOT_CONFIG=y # # Settings # # CONFIG_DESKTOP is not set # CONFIG_EXTRA_COMPAT is not set # CONFIG_FEDORA_COMPAT is not set # CONFIG_INCLUDE_SUSv2 is not set # CONFIG_LONG_OPTS is not set # CONFIG_SHOW_USAGE is not set # CONFIG_FEATURE_VERBOSE_USAGE is not set # CONFIG_FEATURE_COMPRESS_USAGE is not set CONFIG_LFS=y # CONFIG_PAM is not set # CONFIG_FEATURE_DEVPTS is not set # CONFIG_FEATURE_UTMP is not set # CONFIG_FEATURE_WTMP is not set # CONFIG_FEATURE_PIDFILE is not set CONFIG_PID_FILE_PATH="" # CONFIG_BUSYBOX is not set # CONFIG_FEATURE_SHOW_SCRIPT is not set # CONFIG_FEATURE_INSTALLER is not set CONFIG_INSTALL_NO_USR=y # CONFIG_FEATURE_SUID is not set # CONFIG_FEATURE_SUID_CONFIG is not set # CONFIG_FEATURE_SUID_CONFIG_QUIET is not set # CONFIG_FEATURE_PREFER_APPLETS is not set CONFIG_BUSYBOX_EXEC_PATH="/proc/self/exe" # CONFIG_SELINUX is not set # CONFIG_FEATURE_CLEAN_UP is not set # CONFIG_FEATURE_SYSLOG_INFO is not set # CONFIG_FEATURE_SYSLOG is not set # # Build Options # CONFIG_STATIC=y # CONFIG_PIE is not set # CONFIG_NOMMU is not set # CONFIG_BUILD_LIBBUSYBOX is not set # CONFIG_FEATURE_LIBBUSYBOX_STATIC is not set # CONFIG_FEATURE_INDIVIDUAL is not set # CONFIG_FEATURE_SHARED_BUSYBOX is not set CONFIG_CROSS_COMPILER_PREFIX="/usr/bin/musl-" CONFIG_SYSROOT="" CONFIG_EXTRA_CFLAGS="" CONFIG_EXTRA_LDFLAGS="" CONFIG_EXTRA_LDLIBS="" # CONFIG_USE_PORTABLE_CODE is not set # CONFIG_STACK_OPTIMIZATION_386 is not set CONFIG_STATIC_LIBGCC=y # # Installation Options ("make install" behavior) # CONFIG_INSTALL_APPLET_SYMLINKS=y # CONFIG_INSTALL_APPLET_HARDLINKS is not set # CONFIG_INSTALL_APPLET_SCRIPT_WRAPPERS is not set # CONFIG_INSTALL_APPLET_DONT is not set # CONFIG_INSTALL_SH_APPLET_SYMLINK is not set # CONFIG_INSTALL_SH_APPLET_HARDLINK is not set # CONFIG_INSTALL_SH_APPLET_SCRIPT_WRAPPER is not set CONFIG_PREFIX="./_install" # # Debugging Options # CONFIG_DEBUG=y CONFIG_DEBUG_PESSIMIZE=y # CONFIG_DEBUG_SANITIZE is not set # CONFIG_UNIT_TEST is not set # CONFIG_WERROR is not set # CONFIG_WARN_SIMPLE_MSG is not set CONFIG_NO_DEBUG_LIB=y # CONFIG_DMALLOC is not set # CONFIG_EFENCE is not set # # Library Tuning # # CONFIG_FEATURE_USE_BSS_TAIL is not set # CONFIG_FLOAT_DURATION is not set # CONFIG_FEATURE_RTMINMAX is not set # CONFIG_FEATURE_RTMINMAX_USE_LIBC_DEFINITIONS is not set CONFIG_FEATURE_BUFFERS_USE_MALLOC=y # CONFIG_FEATURE_BUFFERS_GO_ON_STACK is not set # CONFIG_FEATURE_BUFFERS_GO_IN_BSS is not set CONFIG_PASSWORD_MINLEN=6 CONFIG_MD5_SMALL=1 CONFIG_SHA1_SMALL=3 # CONFIG_SHA1_HWACCEL is not set # CONFIG_SHA256_HWACCEL is not set CONFIG_SHA3_SMALL=1 # CONFIG_FEATURE_NON_POSIX_CP is not set # CONFIG_FEATURE_VERBOSE_CP_MESSAGE is not set # CONFIG_FEATURE_USE_SENDFILE is not set CONFIG_FEATURE_COPYBUF_KB=4 # CONFIG_MONOTONIC_SYSCALL is not set # CONFIG_IOCTL_HEX2STR_ERROR is not set # CONFIG_FEATURE_EDITING is not set CONFIG_FEATURE_EDITING_MAX_LEN=0 # CONFIG_FEATURE_EDITING_VI is not set CONFIG_FEATURE_EDITING_HISTORY=0 # CONFIG_FEATURE_EDITING_SAVEHISTORY is not set # CONFIG_FEATURE_EDITING_SAVE_ON_EXIT is not set # CONFIG_FEATURE_REVERSE_SEARCH is not set # CONFIG_FEATURE_TAB_COMPLETION is not set # CONFIG_FEATURE_USERNAME_COMPLETION is not set # CONFIG_FEATURE_EDITING_FANCY_PROMPT is not set # CONFIG_FEATURE_EDITING_WINCH is not set # CONFIG_FEATURE_EDITING_ASK_TERMINAL is not set # CONFIG_LOCALE_SUPPORT is not set # CONFIG_UNICODE_SUPPORT is not set # CONFIG_UNICODE_USING_LOCALE is not set # CONFIG_FEATURE_CHECK_UNICODE_IN_ENV is not set CONFIG_SUBST_WCHAR=0 CONFIG_LAST_SUPPORTED_WCHAR=0 # CONFIG_UNICODE_COMBINING_WCHARS is not set # CONFIG_UNICODE_WIDE_WCHARS is not set # CONFIG_UNICODE_BIDI_SUPPORT is not set # CONFIG_UNICODE_NEUTRAL_TABLE is not set # CONFIG_UNICODE_PRESERVE_BROKEN is not set # CONFIG_LOOP_CONFIGURE is not set # CONFIG_NO_LOOP_CONFIGURE is not set CONFIG_TRY_LOOP_CONFIGURE=y # # Applets # # # Archival Utilities # # CONFIG_FEATURE_SEAMLESS_XZ is not set # CONFIG_FEATURE_SEAMLESS_LZMA is not set # CONFIG_FEATURE_SEAMLESS_BZ2 is not set # CONFIG_FEATURE_SEAMLESS_GZ is not set # CONFIG_FEATURE_SEAMLESS_Z is not set # CONFIG_AR is not set # CONFIG_FEATURE_AR_LONG_FILENAMES is not set # CONFIG_FEATURE_AR_CREATE is not set # CONFIG_UNCOMPRESS is not set # CONFIG_GUNZIP is not set # CONFIG_ZCAT is not set # CONFIG_FEATURE_GUNZIP_LONG_OPTIONS is not set # CONFIG_BUNZIP2 is not set # CONFIG_BZCAT is not set # CONFIG_UNLZMA is not set # CONFIG_LZCAT is not set # CONFIG_LZMA is not set # CONFIG_UNXZ is not set # CONFIG_XZCAT is not set # CONFIG_XZ is not set # CONFIG_BZIP2 is not set CONFIG_BZIP2_SMALL=0 # CONFIG_FEATURE_BZIP2_DECOMPRESS is not set # CONFIG_CPIO is not set # CONFIG_FEATURE_CPIO_O is not set # CONFIG_FEATURE_CPIO_P is not set # CONFIG_FEATURE_CPIO_IGNORE_DEVNO is not set # CONFIG_FEATURE_CPIO_RENUMBER_INODES is not set # CONFIG_DPKG is not set # CONFIG_DPKG_DEB is not set # CONFIG_GZIP is not set # CONFIG_FEATURE_GZIP_LONG_OPTIONS is not set CONFIG_GZIP_FAST=0 # CONFIG_FEATURE_GZIP_LEVELS is not set # CONFIG_FEATURE_GZIP_DECOMPRESS is not set # CONFIG_LZOP is not set # CONFIG_UNLZOP is not set # CONFIG_LZOPCAT is not set # CONFIG_LZOP_COMPR_HIGH is not set # CONFIG_RPM is not set # CONFIG_RPM2CPIO is not set # CONFIG_TAR is not set # CONFIG_FEATURE_TAR_LONG_OPTIONS is not set # CONFIG_FEATURE_TAR_CREATE is not set # CONFIG_FEATURE_TAR_AUTODETECT is not set # CONFIG_FEATURE_TAR_FROM is not set # CONFIG_FEATURE_TAR_OLDGNU_COMPATIBILITY is not set # CONFIG_FEATURE_TAR_OLDSUN_COMPATIBILITY is not set # CONFIG_FEATURE_TAR_GNU_EXTENSIONS is not set # CONFIG_FEATURE_TAR_TO_COMMAND is not set # CONFIG_FEATURE_TAR_UNAME_GNAME is not set # CONFIG_FEATURE_TAR_NOPRESERVE_TIME is not set # CONFIG_FEATURE_TAR_SELINUX is not set # CONFIG_UNZIP is not set # CONFIG_FEATURE_UNZIP_CDF is not set # CONFIG_FEATURE_UNZIP_BZIP2 is not set # CONFIG_FEATURE_UNZIP_LZMA is not set # CONFIG_FEATURE_UNZIP_XZ is not set # CONFIG_FEATURE_LZMA_FAST is not set # # Coreutils # # CONFIG_FEATURE_VERBOSE is not set # # Common options for date and touch # # CONFIG_FEATURE_TIMEZONE is not set # # Common options for cp and mv # CONFIG_FEATURE_PRESERVE_HARDLINKS=y # # Common options for df, du, ls # CONFIG_FEATURE_HUMAN_READABLE=y # CONFIG_BASENAME is not set CONFIG_CAT=y CONFIG_FEATURE_CATN=y CONFIG_FEATURE_CATV=y # CONFIG_CHGRP is not set # CONFIG_CHMOD is not set # CONFIG_CHOWN is not set # CONFIG_FEATURE_CHOWN_LONG_OPTIONS is not set # CONFIG_CHROOT is not set # CONFIG_CKSUM is not set # CONFIG_CRC32 is not set # CONFIG_COMM is not set CONFIG_CP=y # CONFIG_FEATURE_CP_LONG_OPTIONS is not set # CONFIG_FEATURE_CP_REFLINK is not set # CONFIG_CUT is not set # CONFIG_FEATURE_CUT_REGEX is not set # CONFIG_DATE is not set # CONFIG_FEATURE_DATE_ISOFMT is not set # CONFIG_FEATURE_DATE_NANO is not set # CONFIG_FEATURE_DATE_COMPAT is not set # CONFIG_DD is not set # CONFIG_FEATURE_DD_SIGNAL_HANDLING is not set # CONFIG_FEATURE_DD_THIRD_STATUS_LINE is not set # CONFIG_FEATURE_DD_IBS_OBS is not set # CONFIG_FEATURE_DD_STATUS is not set # CONFIG_DF is not set # CONFIG_FEATURE_DF_FANCY is not set # CONFIG_FEATURE_SKIP_ROOTFS is not set # CONFIG_DIRNAME is not set # CONFIG_DOS2UNIX is not set # CONFIG_UNIX2DOS is not set # CONFIG_DU is not set # CONFIG_FEATURE_DU_DEFAULT_BLOCKSIZE_1K is not set CONFIG_ECHO=y CONFIG_FEATURE_FANCY_ECHO=y # CONFIG_ENV is not set # CONFIG_EXPAND is not set # CONFIG_UNEXPAND is not set # CONFIG_EXPR is not set # CONFIG_EXPR_MATH_SUPPORT_64 is not set # CONFIG_FACTOR is not set # CONFIG_FALSE is not set # CONFIG_FOLD is not set # CONFIG_HEAD is not set # CONFIG_FEATURE_FANCY_HEAD is not set # CONFIG_HOSTID is not set # CONFIG_ID is not set # CONFIG_GROUPS is not set # CONFIG_INSTALL is not set # CONFIG_FEATURE_INSTALL_LONG_OPTIONS is not set # CONFIG_LINK is not set CONFIG_LN=y # CONFIG_LOGNAME is not set CONFIG_LS=y CONFIG_FEATURE_LS_FILETYPES=y CONFIG_FEATURE_LS_FOLLOWLINKS=y CONFIG_FEATURE_LS_RECURSIVE=y CONFIG_FEATURE_LS_WIDTH=y CONFIG_FEATURE_LS_SORTFILES=y # CONFIG_FEATURE_LS_TIMESTAMPS is not set # CONFIG_FEATURE_LS_USERNAME is not set # CONFIG_FEATURE_LS_COLOR is not set # CONFIG_FEATURE_LS_COLOR_IS_DEFAULT is not set # CONFIG_MD5SUM is not set # CONFIG_SHA1SUM is not set # CONFIG_SHA256SUM is not set # CONFIG_SHA512SUM is not set # CONFIG_SHA3SUM is not set # CONFIG_FEATURE_MD5_SHA1_SUM_CHECK is not set CONFIG_MKDIR=y # CONFIG_MKFIFO is not set # CONFIG_MKNOD is not set # CONFIG_MKTEMP is not set CONFIG_MV=y # CONFIG_NICE is not set # CONFIG_NL is not set # CONFIG_NOHUP is not set # CONFIG_NPROC is not set # CONFIG_OD is not set # CONFIG_PASTE is not set # CONFIG_PRINTENV is not set # CONFIG_PRINTF is not set CONFIG_PWD=y # CONFIG_READLINK is not set # CONFIG_FEATURE_READLINK_FOLLOW is not set # CONFIG_REALPATH is not set CONFIG_RM=y CONFIG_RMDIR=y # CONFIG_SEQ is not set # CONFIG_SHRED is not set # CONFIG_SHUF is not set # CONFIG_SLEEP is not set # CONFIG_FEATURE_FANCY_SLEEP is not set # CONFIG_SORT is not set # CONFIG_FEATURE_SORT_BIG is not set # CONFIG_FEATURE_SORT_OPTIMIZE_MEMORY is not set # CONFIG_SPLIT is not set # CONFIG_FEATURE_SPLIT_FANCY is not set CONFIG_STAT=y # CONFIG_FEATURE_STAT_FORMAT is not set # CONFIG_FEATURE_STAT_FILESYSTEM is not set # CONFIG_STTY is not set # CONFIG_SUM is not set # CONFIG_SYNC is not set # CONFIG_FEATURE_SYNC_FANCY is not set # CONFIG_FSYNC is not set # CONFIG_TAC is not set # CONFIG_TAIL is not set # CONFIG_FEATURE_FANCY_TAIL is not set # CONFIG_TEE is not set # CONFIG_FEATURE_TEE_USE_BLOCK_IO is not set # CONFIG_TEST is not set # CONFIG_TEST1 is not set # CONFIG_TEST2 is not set # CONFIG_FEATURE_TEST_64 is not set # CONFIG_TIMEOUT is not set # CONFIG_TOUCH is not set # CONFIG_FEATURE_TOUCH_SUSV3 is not set # CONFIG_TR is not set # CONFIG_FEATURE_TR_CLASSES is not set # CONFIG_FEATURE_TR_EQUIV is not set # CONFIG_TRUE is not set # CONFIG_TRUNCATE is not set # CONFIG_TSORT is not set # CONFIG_TTY is not set # CONFIG_UNAME is not set CONFIG_UNAME_OSNAME="" # CONFIG_BB_ARCH is not set # CONFIG_UNIQ is not set # CONFIG_UNLINK is not set # CONFIG_USLEEP is not set # CONFIG_UUDECODE is not set # CONFIG_BASE32 is not set # CONFIG_BASE64 is not set # CONFIG_UUENCODE is not set # CONFIG_WC is not set # CONFIG_FEATURE_WC_LARGE is not set # CONFIG_WHO is not set # CONFIG_W is not set # CONFIG_USERS is not set # CONFIG_WHOAMI is not set # CONFIG_YES is not set # # Console Utilities # # CONFIG_CHVT is not set # CONFIG_CLEAR is not set # CONFIG_DEALLOCVT is not set # CONFIG_DUMPKMAP is not set # CONFIG_FGCONSOLE is not set # CONFIG_KBD_MODE is not set # CONFIG_LOADFONT is not set # CONFIG_SETFONT is not set # CONFIG_FEATURE_SETFONT_TEXTUAL_MAP is not set CONFIG_DEFAULT_SETFONT_DIR="" # CONFIG_FEATURE_LOADFONT_PSF2 is not set # CONFIG_FEATURE_LOADFONT_RAW is not set # CONFIG_LOADKMAP is not set # CONFIG_OPENVT is not set # CONFIG_RESET is not set # CONFIG_RESIZE is not set # CONFIG_FEATURE_RESIZE_PRINT is not set # CONFIG_SETCONSOLE is not set # CONFIG_FEATURE_SETCONSOLE_LONG_OPTIONS is not set # CONFIG_SETKEYCODES is not set # CONFIG_SETLOGCONS is not set # CONFIG_SHOWKEY is not set # # Debian Utilities # # CONFIG_PIPE_PROGRESS is not set # CONFIG_RUN_PARTS is not set # CONFIG_FEATURE_RUN_PARTS_LONG_OPTIONS is not set # CONFIG_FEATURE_RUN_PARTS_FANCY is not set # CONFIG_START_STOP_DAEMON is not set # CONFIG_FEATURE_START_STOP_DAEMON_LONG_OPTIONS is not set # CONFIG_FEATURE_START_STOP_DAEMON_FANCY is not set # CONFIG_WHICH is not set # # klibc-utils # # CONFIG_MINIPS is not set # CONFIG_NUKE is not set # CONFIG_RESUME is not set # CONFIG_RUN_INIT is not set # # Editors # # CONFIG_AWK is not set # CONFIG_FEATURE_AWK_LIBM is not set # CONFIG_FEATURE_AWK_GNU_EXTENSIONS is not set # CONFIG_CMP is not set # CONFIG_DIFF is not set # CONFIG_FEATURE_DIFF_LONG_OPTIONS is not set # CONFIG_FEATURE_DIFF_DIR is not set # CONFIG_ED is not set # CONFIG_PATCH is not set # CONFIG_SED is not set # CONFIG_VI is not set CONFIG_FEATURE_VI_MAX_LEN=0 # CONFIG_FEATURE_VI_8BIT is not set # CONFIG_FEATURE_VI_COLON is not set # CONFIG_FEATURE_VI_COLON_EXPAND is not set # CONFIG_FEATURE_VI_YANKMARK is not set # CONFIG_FEATURE_VI_SEARCH is not set # CONFIG_FEATURE_VI_REGEX_SEARCH is not set # CONFIG_FEATURE_VI_USE_SIGNALS is not set # CONFIG_FEATURE_VI_DOT_CMD is not set # CONFIG_FEATURE_VI_READONLY is not set # CONFIG_FEATURE_VI_SETOPTS is not set # CONFIG_FEATURE_VI_SET is not set # CONFIG_FEATURE_VI_WIN_RESIZE is not set # CONFIG_FEATURE_VI_ASK_TERMINAL is not set # CONFIG_FEATURE_VI_UNDO is not set # CONFIG_FEATURE_VI_UNDO_QUEUE is not set CONFIG_FEATURE_VI_UNDO_QUEUE_MAX=0 # CONFIG_FEATURE_VI_VERBOSE_STATUS is not set # CONFIG_FEATURE_ALLOW_EXEC is not set # # Finding Utilities # # CONFIG_FIND is not set # CONFIG_FEATURE_FIND_PRINT0 is not set # CONFIG_FEATURE_FIND_MTIME is not set # CONFIG_FEATURE_FIND_ATIME is not set # CONFIG_FEATURE_FIND_CTIME is not set # CONFIG_FEATURE_FIND_MMIN is not set # CONFIG_FEATURE_FIND_AMIN is not set # CONFIG_FEATURE_FIND_CMIN is not set # CONFIG_FEATURE_FIND_PERM is not set # CONFIG_FEATURE_FIND_TYPE is not set # CONFIG_FEATURE_FIND_EXECUTABLE is not set # CONFIG_FEATURE_FIND_XDEV is not set # CONFIG_FEATURE_FIND_MAXDEPTH is not set # CONFIG_FEATURE_FIND_NEWER is not set # CONFIG_FEATURE_FIND_INUM is not set # CONFIG_FEATURE_FIND_SAMEFILE is not set # CONFIG_FEATURE_FIND_EXEC is not set # CONFIG_FEATURE_FIND_EXEC_PLUS is not set # CONFIG_FEATURE_FIND_USER is not set # CONFIG_FEATURE_FIND_GROUP is not set # CONFIG_FEATURE_FIND_NOT is not set # CONFIG_FEATURE_FIND_DEPTH is not set # CONFIG_FEATURE_FIND_PAREN is not set # CONFIG_FEATURE_FIND_SIZE is not set # CONFIG_FEATURE_FIND_PRUNE is not set # CONFIG_FEATURE_FIND_QUIT is not set # CONFIG_FEATURE_FIND_DELETE is not set # CONFIG_FEATURE_FIND_EMPTY is not set # CONFIG_FEATURE_FIND_PATH is not set # CONFIG_FEATURE_FIND_REGEX is not set # CONFIG_FEATURE_FIND_CONTEXT is not set # CONFIG_FEATURE_FIND_LINKS is not set # CONFIG_GREP is not set # CONFIG_EGREP is not set # CONFIG_FGREP is not set # CONFIG_FEATURE_GREP_CONTEXT is not set # CONFIG_XARGS is not set # CONFIG_FEATURE_XARGS_SUPPORT_CONFIRMATION is not set # CONFIG_FEATURE_XARGS_SUPPORT_QUOTES is not set # CONFIG_FEATURE_XARGS_SUPPORT_TERMOPT is not set # CONFIG_FEATURE_XARGS_SUPPORT_ZERO_TERM is not set # CONFIG_FEATURE_XARGS_SUPPORT_REPL_STR is not set # CONFIG_FEATURE_XARGS_SUPPORT_PARALLEL is not set # CONFIG_FEATURE_XARGS_SUPPORT_ARGS_FILE is not set # # Init Utilities # # CONFIG_BOOTCHARTD is not set # CONFIG_FEATURE_BOOTCHARTD_BLOATED_HEADER is not set # CONFIG_FEATURE_BOOTCHARTD_CONFIG_FILE is not set # CONFIG_HALT is not set # CONFIG_POWEROFF is not set # CONFIG_REBOOT is not set # CONFIG_FEATURE_WAIT_FOR_INIT is not set # CONFIG_FEATURE_CALL_TELINIT is not set CONFIG_TELINIT_PATH="" # CONFIG_INIT is not set # CONFIG_LINUXRC is not set # CONFIG_FEATURE_USE_INITTAB is not set # CONFIG_FEATURE_KILL_REMOVED is not set CONFIG_FEATURE_KILL_DELAY=0 # CONFIG_FEATURE_INIT_SCTTY is not set # CONFIG_FEATURE_INIT_SYSLOG is not set # CONFIG_FEATURE_INIT_QUIET is not set # CONFIG_FEATURE_INIT_COREDUMPS is not set CONFIG_INIT_TERMINAL_TYPE="" # CONFIG_FEATURE_INIT_MODIFY_CMDLINE is not set # # Login/Password Management Utilities # # CONFIG_FEATURE_SHADOWPASSWDS is not set # CONFIG_USE_BB_PWD_GRP is not set # CONFIG_USE_BB_SHADOW is not set # CONFIG_USE_BB_CRYPT is not set # CONFIG_USE_BB_CRYPT_SHA is not set # CONFIG_ADD_SHELL is not set # CONFIG_REMOVE_SHELL is not set # CONFIG_ADDGROUP is not set # CONFIG_FEATURE_ADDUSER_TO_GROUP is not set # CONFIG_ADDUSER is not set # CONFIG_FEATURE_CHECK_NAMES is not set CONFIG_LAST_ID=0 CONFIG_FIRST_SYSTEM_ID=0 CONFIG_LAST_SYSTEM_ID=0 # CONFIG_CHPASSWD is not set CONFIG_FEATURE_DEFAULT_PASSWD_ALGO="" # CONFIG_CRYPTPW is not set # CONFIG_MKPASSWD is not set # CONFIG_DELUSER is not set # CONFIG_DELGROUP is not set # CONFIG_FEATURE_DEL_USER_FROM_GROUP is not set # CONFIG_GETTY is not set # CONFIG_LOGIN is not set # CONFIG_LOGIN_SESSION_AS_CHILD is not set # CONFIG_LOGIN_SCRIPTS is not set # CONFIG_FEATURE_NOLOGIN is not set # CONFIG_FEATURE_SECURETTY is not set # CONFIG_PASSWD is not set # CONFIG_FEATURE_PASSWD_WEAK_CHECK is not set # CONFIG_SU is not set # CONFIG_FEATURE_SU_SYSLOG is not set # CONFIG_FEATURE_SU_CHECKS_SHELLS is not set # CONFIG_FEATURE_SU_BLANK_PW_NEEDS_SECURE_TTY is not set # CONFIG_SULOGIN is not set # CONFIG_VLOCK is not set # # Linux Ext2 FS Progs # # CONFIG_CHATTR is not set # CONFIG_FSCK is not set # CONFIG_LSATTR is not set # CONFIG_TUNE2FS is not set # # Linux Module Utilities # # CONFIG_MODPROBE_SMALL is not set # CONFIG_DEPMOD is not set # CONFIG_INSMOD is not set # CONFIG_LSMOD is not set # CONFIG_FEATURE_LSMOD_PRETTY_2_6_OUTPUT is not set # CONFIG_MODINFO is not set # CONFIG_MODPROBE is not set # CONFIG_FEATURE_MODPROBE_BLACKLIST is not set # CONFIG_RMMOD is not set # # Options common to multiple modutils # # CONFIG_FEATURE_CMDLINE_MODULE_OPTIONS is not set # CONFIG_FEATURE_MODPROBE_SMALL_CHECK_ALREADY_LOADED is not set # CONFIG_FEATURE_2_4_MODULES is not set # CONFIG_FEATURE_INSMOD_VERSION_CHECKING is not set # CONFIG_FEATURE_INSMOD_KSYMOOPS_SYMBOLS is not set # CONFIG_FEATURE_INSMOD_LOADINKMEM is not set # CONFIG_FEATURE_INSMOD_LOAD_MAP is not set # CONFIG_FEATURE_INSMOD_LOAD_MAP_FULL is not set # CONFIG_FEATURE_CHECK_TAINTED_MODULE is not set # CONFIG_FEATURE_INSMOD_TRY_MMAP is not set # CONFIG_FEATURE_MODUTILS_ALIAS is not set # CONFIG_FEATURE_MODUTILS_SYMBOLS is not set CONFIG_DEFAULT_MODULES_DIR="" CONFIG_DEFAULT_DEPMOD_FILE="" # # Linux System Utilities # # CONFIG_ACPID is not set # CONFIG_FEATURE_ACPID_COMPAT is not set # CONFIG_BLKDISCARD is not set # CONFIG_BLKID is not set # CONFIG_FEATURE_BLKID_TYPE is not set # CONFIG_BLOCKDEV is not set # CONFIG_CAL is not set # CONFIG_CHRT is not set # CONFIG_DMESG is not set # CONFIG_FEATURE_DMESG_PRETTY is not set # CONFIG_EJECT is not set # CONFIG_FEATURE_EJECT_SCSI is not set # CONFIG_FALLOCATE is not set # CONFIG_FATATTR is not set # CONFIG_FBSET is not set # CONFIG_FEATURE_FBSET_FANCY is not set # CONFIG_FEATURE_FBSET_READMODE is not set # CONFIG_FDFORMAT is not set # CONFIG_FDISK is not set # CONFIG_FDISK_SUPPORT_LARGE_DISKS is not set # CONFIG_FEATURE_FDISK_WRITABLE is not set # CONFIG_FEATURE_AIX_LABEL is not set # CONFIG_FEATURE_SGI_LABEL is not set # CONFIG_FEATURE_SUN_LABEL is not set # CONFIG_FEATURE_OSF_LABEL is not set # CONFIG_FEATURE_GPT_LABEL is not set # CONFIG_FEATURE_FDISK_ADVANCED is not set # CONFIG_FINDFS is not set # CONFIG_FLOCK is not set # CONFIG_FDFLUSH is not set # CONFIG_FREERAMDISK is not set # CONFIG_FSCK_MINIX is not set # CONFIG_FSFREEZE is not set # CONFIG_FSTRIM is not set # CONFIG_GETOPT is not set # CONFIG_FEATURE_GETOPT_LONG is not set # CONFIG_HEXDUMP is not set # CONFIG_HD is not set # CONFIG_XXD is not set # CONFIG_HWCLOCK is not set # CONFIG_FEATURE_HWCLOCK_ADJTIME_FHS is not set # CONFIG_IONICE is not set # CONFIG_IPCRM is not set # CONFIG_IPCS is not set # CONFIG_LAST is not set # CONFIG_FEATURE_LAST_FANCY is not set # CONFIG_LOSETUP is not set # CONFIG_LSPCI is not set # CONFIG_LSUSB is not set # CONFIG_MDEV is not set # CONFIG_FEATURE_MDEV_CONF is not set # CONFIG_FEATURE_MDEV_RENAME is not set # CONFIG_FEATURE_MDEV_RENAME_REGEXP is not set # CONFIG_FEATURE_MDEV_EXEC is not set # CONFIG_FEATURE_MDEV_LOAD_FIRMWARE is not set # CONFIG_FEATURE_MDEV_DAEMON is not set # CONFIG_MESG is not set # CONFIG_FEATURE_MESG_ENABLE_ONLY_GROUP is not set # CONFIG_MKE2FS is not set # CONFIG_MKFS_EXT2 is not set # CONFIG_MKFS_MINIX is not set # CONFIG_FEATURE_MINIX2 is not set # CONFIG_MKFS_REISER is not set # CONFIG_MKDOSFS is not set # CONFIG_MKFS_VFAT is not set # CONFIG_MKSWAP is not set # CONFIG_FEATURE_MKSWAP_UUID is not set # CONFIG_MORE is not set # CONFIG_MOUNT is not set # CONFIG_FEATURE_MOUNT_FAKE is not set # CONFIG_FEATURE_MOUNT_VERBOSE is not set # CONFIG_FEATURE_MOUNT_HELPERS is not set # CONFIG_FEATURE_MOUNT_LABEL is not set # CONFIG_FEATURE_MOUNT_NFS is not set # CONFIG_FEATURE_MOUNT_CIFS is not set # CONFIG_FEATURE_MOUNT_FLAGS is not set # CONFIG_FEATURE_MOUNT_FSTAB is not set # CONFIG_FEATURE_MOUNT_OTHERTAB is not set # CONFIG_MOUNTPOINT is not set CONFIG_NOLOGIN=y # CONFIG_NOLOGIN_DEPENDENCIES is not set # CONFIG_NSENTER is not set # CONFIG_PIVOT_ROOT is not set # CONFIG_RDATE is not set # CONFIG_RDEV is not set # CONFIG_READPROFILE is not set # CONFIG_RENICE is not set # CONFIG_REV is not set # CONFIG_RTCWAKE is not set # CONFIG_SCRIPT is not set # CONFIG_SCRIPTREPLAY is not set # CONFIG_SETARCH is not set # CONFIG_LINUX32 is not set # CONFIG_LINUX64 is not set # CONFIG_SETPRIV is not set # CONFIG_FEATURE_SETPRIV_DUMP is not set # CONFIG_FEATURE_SETPRIV_CAPABILITIES is not set # CONFIG_FEATURE_SETPRIV_CAPABILITY_NAMES is not set # CONFIG_SETSID is not set # CONFIG_SWAPON is not set # CONFIG_FEATURE_SWAPON_DISCARD is not set # CONFIG_FEATURE_SWAPON_PRI is not set # CONFIG_SWAPOFF is not set # CONFIG_FEATURE_SWAPONOFF_LABEL is not set # CONFIG_SWITCH_ROOT is not set # CONFIG_TASKSET is not set # CONFIG_FEATURE_TASKSET_FANCY is not set # CONFIG_FEATURE_TASKSET_CPULIST is not set # CONFIG_UEVENT is not set # CONFIG_UMOUNT is not set # CONFIG_FEATURE_UMOUNT_ALL is not set # CONFIG_UNSHARE is not set # CONFIG_WALL is not set # CONFIG_FEATURE_MOUNT_LOOP is not set # CONFIG_FEATURE_MOUNT_LOOP_CREATE is not set # CONFIG_FEATURE_MTAB_SUPPORT is not set # CONFIG_VOLUMEID is not set # CONFIG_FEATURE_VOLUMEID_BCACHE is not set # CONFIG_FEATURE_VOLUMEID_BTRFS is not set # CONFIG_FEATURE_VOLUMEID_CRAMFS is not set # CONFIG_FEATURE_VOLUMEID_EROFS is not set # CONFIG_FEATURE_VOLUMEID_EXFAT is not set # CONFIG_FEATURE_VOLUMEID_EXT is not set # CONFIG_FEATURE_VOLUMEID_F2FS is not set # CONFIG_FEATURE_VOLUMEID_FAT is not set # CONFIG_FEATURE_VOLUMEID_HFS is not set # CONFIG_FEATURE_VOLUMEID_ISO9660 is not set # CONFIG_FEATURE_VOLUMEID_JFS is not set # CONFIG_FEATURE_VOLUMEID_LFS is not set # CONFIG_FEATURE_VOLUMEID_LINUXRAID is not set # CONFIG_FEATURE_VOLUMEID_LINUXSWAP is not set # CONFIG_FEATURE_VOLUMEID_LUKS is not set # CONFIG_FEATURE_VOLUMEID_MINIX is not set # CONFIG_FEATURE_VOLUMEID_NILFS is not set # CONFIG_FEATURE_VOLUMEID_NTFS is not set # CONFIG_FEATURE_VOLUMEID_OCFS2 is not set # CONFIG_FEATURE_VOLUMEID_REISERFS is not set # CONFIG_FEATURE_VOLUMEID_ROMFS is not set # CONFIG_FEATURE_VOLUMEID_SQUASHFS is not set # CONFIG_FEATURE_VOLUMEID_SYSV is not set # CONFIG_FEATURE_VOLUMEID_UBIFS is not set # CONFIG_FEATURE_VOLUMEID_UDF is not set # CONFIG_FEATURE_VOLUMEID_XFS is not set # # Miscellaneous Utilities # # CONFIG_ADJTIMEX is not set # CONFIG_ASCII is not set # CONFIG_BBCONFIG is not set # CONFIG_FEATURE_COMPRESS_BBCONFIG is not set # CONFIG_BC is not set # CONFIG_DC is not set # CONFIG_FEATURE_DC_BIG is not set # CONFIG_FEATURE_DC_LIBM is not set # CONFIG_FEATURE_BC_INTERACTIVE is not set # CONFIG_FEATURE_BC_LONG_OPTIONS is not set # CONFIG_BEEP is not set CONFIG_FEATURE_BEEP_FREQ=0 CONFIG_FEATURE_BEEP_LENGTH_MS=0 # CONFIG_CHAT is not set # CONFIG_FEATURE_CHAT_NOFAIL is not set # CONFIG_FEATURE_CHAT_TTY_HIFI is not set # CONFIG_FEATURE_CHAT_IMPLICIT_CR is not set # CONFIG_FEATURE_CHAT_SWALLOW_OPTS is not set # CONFIG_FEATURE_CHAT_SEND_ESCAPES is not set # CONFIG_FEATURE_CHAT_VAR_ABORT_LEN is not set # CONFIG_FEATURE_CHAT_CLR_ABORT is not set # CONFIG_CONSPY is not set # CONFIG_CROND is not set # CONFIG_FEATURE_CROND_D is not set # CONFIG_FEATURE_CROND_CALL_SENDMAIL is not set # CONFIG_FEATURE_CROND_SPECIAL_TIMES is not set CONFIG_FEATURE_CROND_DIR="" # CONFIG_CRONTAB is not set # CONFIG_DEVFSD is not set # CONFIG_DEVFSD_MODLOAD is not set # CONFIG_DEVFSD_FG_NP is not set # CONFIG_DEVFSD_VERBOSE is not set # CONFIG_FEATURE_DEVFS is not set # CONFIG_DEVMEM is not set # CONFIG_FBSPLASH is not set # CONFIG_FLASH_ERASEALL is not set # CONFIG_FLASH_LOCK is not set # CONFIG_FLASH_UNLOCK is not set # CONFIG_FLASHCP is not set # CONFIG_HDPARM is not set # CONFIG_FEATURE_HDPARM_GET_IDENTITY is not set # CONFIG_FEATURE_HDPARM_HDIO_SCAN_HWIF is not set # CONFIG_FEATURE_HDPARM_HDIO_UNREGISTER_HWIF is not set # CONFIG_FEATURE_HDPARM_HDIO_DRIVE_RESET is not set # CONFIG_FEATURE_HDPARM_HDIO_TRISTATE_HWIF is not set # CONFIG_FEATURE_HDPARM_HDIO_GETSET_DMA is not set # CONFIG_HEXEDIT is not set # CONFIG_I2CGET is not set # CONFIG_I2CSET is not set # CONFIG_I2CDUMP is not set # CONFIG_I2CDETECT is not set # CONFIG_I2CTRANSFER is not set # CONFIG_INOTIFYD is not set # CONFIG_LESS is not set CONFIG_FEATURE_LESS_MAXLINES=0 # CONFIG_FEATURE_LESS_BRACKETS is not set # CONFIG_FEATURE_LESS_FLAGS is not set # CONFIG_FEATURE_LESS_TRUNCATE is not set # CONFIG_FEATURE_LESS_MARKS is not set # CONFIG_FEATURE_LESS_REGEXP is not set # CONFIG_FEATURE_LESS_WINCH is not set # CONFIG_FEATURE_LESS_ASK_TERMINAL is not set # CONFIG_FEATURE_LESS_DASHCMD is not set # CONFIG_FEATURE_LESS_LINENUMS is not set # CONFIG_FEATURE_LESS_RAW is not set # CONFIG_FEATURE_LESS_ENV is not set # CONFIG_LSSCSI is not set # CONFIG_MAKEDEVS is not set # CONFIG_FEATURE_MAKEDEVS_LEAF is not set # CONFIG_FEATURE_MAKEDEVS_TABLE is not set # CONFIG_MAN is not set # CONFIG_MICROCOM is not set CONFIG_MIM=y # CONFIG_MT is not set # CONFIG_NANDWRITE is not set # CONFIG_NANDDUMP is not set # CONFIG_PARTPROBE is not set # CONFIG_RAIDAUTORUN is not set CONFIG_READAHEAD=y # CONFIG_RFKILL is not set # CONFIG_RUNLEVEL is not set # CONFIG_RX is not set # CONFIG_SEEDRNG is not set # CONFIG_SETFATTR is not set # CONFIG_SETSERIAL is not set # CONFIG_STRINGS is not set # CONFIG_TIME is not set # CONFIG_TREE is not set # CONFIG_TS is not set # CONFIG_TTYSIZE is not set # CONFIG_UBIATTACH is not set # CONFIG_UBIDETACH is not set # CONFIG_UBIMKVOL is not set # CONFIG_UBIRMVOL is not set # CONFIG_UBIRSVOL is not set # CONFIG_UBIUPDATEVOL is not set # CONFIG_UBIRENAME is not set # CONFIG_VOLNAME is not set # CONFIG_WATCHDOG is not set # CONFIG_FEATURE_WATCHDOG_OPEN_TWICE is not set # # Networking Utilities # # CONFIG_FEATURE_IPV6 is not set # CONFIG_FEATURE_UNIX_LOCAL is not set # CONFIG_FEATURE_PREFER_IPV4_ADDRESS is not set # CONFIG_VERBOSE_RESOLUTION_ERRORS is not set # CONFIG_FEATURE_ETC_NETWORKS is not set # CONFIG_FEATURE_ETC_SERVICES is not set # CONFIG_FEATURE_HWIB is not set # CONFIG_FEATURE_TLS_SHA1 is not set # CONFIG_ARP is not set # CONFIG_ARPING is not set # CONFIG_BRCTL is not set # CONFIG_FEATURE_BRCTL_FANCY is not set # CONFIG_FEATURE_BRCTL_SHOW is not set # CONFIG_DNSD is not set # CONFIG_ETHER_WAKE is not set # CONFIG_FTPD is not set # CONFIG_FEATURE_FTPD_WRITE is not set # CONFIG_FEATURE_FTPD_ACCEPT_BROKEN_LIST is not set # CONFIG_FEATURE_FTPD_AUTHENTICATION is not set # CONFIG_FTPGET is not set # CONFIG_FTPPUT is not set # CONFIG_FEATURE_FTPGETPUT_LONG_OPTIONS is not set # CONFIG_HOSTNAME is not set # CONFIG_DNSDOMAINNAME is not set # CONFIG_HTTPD is not set CONFIG_FEATURE_HTTPD_PORT_DEFAULT=0 # CONFIG_FEATURE_HTTPD_RANGES is not set # CONFIG_FEATURE_HTTPD_SETUID is not set # CONFIG_FEATURE_HTTPD_BASIC_AUTH is not set # CONFIG_FEATURE_HTTPD_AUTH_MD5 is not set # CONFIG_FEATURE_HTTPD_CGI is not set # CONFIG_FEATURE_HTTPD_CONFIG_WITH_SCRIPT_INTERPR is not set # CONFIG_FEATURE_HTTPD_SET_REMOTE_PORT_TO_ENV is not set # CONFIG_FEATURE_HTTPD_ENCODE_URL_STR is not set # CONFIG_FEATURE_HTTPD_ERROR_PAGES is not set # CONFIG_FEATURE_HTTPD_PROXY is not set # CONFIG_FEATURE_HTTPD_GZIP is not set # CONFIG_FEATURE_HTTPD_ETAG is not set # CONFIG_FEATURE_HTTPD_LAST_MODIFIED is not set # CONFIG_FEATURE_HTTPD_DATE is not set # CONFIG_FEATURE_HTTPD_ACL_IP is not set # CONFIG_IFCONFIG is not set # CONFIG_FEATURE_IFCONFIG_STATUS is not set # CONFIG_FEATURE_IFCONFIG_SLIP is not set # CONFIG_FEATURE_IFCONFIG_MEMSTART_IOADDR_IRQ is not set # CONFIG_FEATURE_IFCONFIG_HW is not set # CONFIG_FEATURE_IFCONFIG_BROADCAST_PLUS is not set # CONFIG_IFENSLAVE is not set # CONFIG_IFPLUGD is not set # CONFIG_IFUP is not set # CONFIG_IFDOWN is not set CONFIG_IFUPDOWN_IFSTATE_PATH="" # CONFIG_FEATURE_IFUPDOWN_IP is not set # CONFIG_FEATURE_IFUPDOWN_IPV4 is not set # CONFIG_FEATURE_IFUPDOWN_IPV6 is not set # CONFIG_FEATURE_IFUPDOWN_MAPPING is not set # CONFIG_FEATURE_IFUPDOWN_EXTERNAL_DHCP is not set # CONFIG_INETD is not set # CONFIG_FEATURE_INETD_SUPPORT_BUILTIN_ECHO is not set # CONFIG_FEATURE_INETD_SUPPORT_BUILTIN_DISCARD is not set # CONFIG_FEATURE_INETD_SUPPORT_BUILTIN_TIME is not set # CONFIG_FEATURE_INETD_SUPPORT_BUILTIN_DAYTIME is not set # CONFIG_FEATURE_INETD_SUPPORT_BUILTIN_CHARGEN is not set # CONFIG_FEATURE_INETD_RPC is not set # CONFIG_IP is not set # CONFIG_IPADDR is not set # CONFIG_IPLINK is not set # CONFIG_IPROUTE is not set # CONFIG_IPTUNNEL is not set # CONFIG_IPRULE is not set # CONFIG_IPNEIGH is not set # CONFIG_FEATURE_IP_ADDRESS is not set # CONFIG_FEATURE_IP_LINK is not set # CONFIG_FEATURE_IP_ROUTE is not set CONFIG_FEATURE_IP_ROUTE_DIR="" # CONFIG_FEATURE_IP_TUNNEL is not set # CONFIG_FEATURE_IP_RULE is not set # CONFIG_FEATURE_IP_NEIGH is not set # CONFIG_FEATURE_IP_RARE_PROTOCOLS is not set # CONFIG_IPCALC is not set # CONFIG_FEATURE_IPCALC_LONG_OPTIONS is not set # CONFIG_FEATURE_IPCALC_FANCY is not set # CONFIG_FAKEIDENTD is not set # CONFIG_NAMEIF is not set # CONFIG_FEATURE_NAMEIF_EXTENDED is not set # CONFIG_NBDCLIENT is not set # CONFIG_NC is not set # CONFIG_NETCAT is not set # CONFIG_NC_SERVER is not set # CONFIG_NC_EXTRA is not set # CONFIG_NC_110_COMPAT is not set # CONFIG_NETSTAT is not set # CONFIG_FEATURE_NETSTAT_WIDE is not set # CONFIG_FEATURE_NETSTAT_PRG is not set # CONFIG_NSLOOKUP is not set # CONFIG_FEATURE_NSLOOKUP_BIG is not set # CONFIG_FEATURE_NSLOOKUP_LONG_OPTIONS is not set # CONFIG_NTPD is not set # CONFIG_FEATURE_NTPD_SERVER is not set # CONFIG_FEATURE_NTPD_CONF is not set # CONFIG_FEATURE_NTP_AUTH is not set # CONFIG_PING is not set # CONFIG_PING6 is not set # CONFIG_FEATURE_FANCY_PING is not set # CONFIG_PSCAN is not set # CONFIG_ROUTE is not set # CONFIG_SLATTACH is not set # CONFIG_SSL_CLIENT is not set # CONFIG_TC is not set # CONFIG_FEATURE_TC_INGRESS is not set # CONFIG_TCPSVD is not set # CONFIG_UDPSVD is not set # CONFIG_TELNET is not set # CONFIG_FEATURE_TELNET_TTYPE is not set # CONFIG_FEATURE_TELNET_AUTOLOGIN is not set # CONFIG_FEATURE_TELNET_WIDTH is not set # CONFIG_TELNETD is not set # CONFIG_FEATURE_TELNETD_STANDALONE is not set CONFIG_FEATURE_TELNETD_PORT_DEFAULT=0 # CONFIG_FEATURE_TELNETD_INETD_WAIT is not set # CONFIG_TFTP is not set # CONFIG_FEATURE_TFTP_PROGRESS_BAR is not set # CONFIG_FEATURE_TFTP_HPA_COMPAT is not set # CONFIG_TFTPD is not set # CONFIG_FEATURE_TFTP_GET is not set # CONFIG_FEATURE_TFTP_PUT is not set # CONFIG_FEATURE_TFTP_BLOCKSIZE is not set # CONFIG_TFTP_DEBUG is not set # CONFIG_TLS is not set # CONFIG_TRACEROUTE is not set # CONFIG_TRACEROUTE6 is not set # CONFIG_FEATURE_TRACEROUTE_VERBOSE is not set # CONFIG_FEATURE_TRACEROUTE_USE_ICMP is not set # CONFIG_TUNCTL is not set # CONFIG_FEATURE_TUNCTL_UG is not set # CONFIG_VCONFIG is not set # CONFIG_WGET is not set # CONFIG_FEATURE_WGET_LONG_OPTIONS is not set # CONFIG_FEATURE_WGET_STATUSBAR is not set # CONFIG_FEATURE_WGET_FTP is not set # CONFIG_FEATURE_WGET_AUTHENTICATION is not set # CONFIG_FEATURE_WGET_TIMEOUT is not set # CONFIG_FEATURE_WGET_HTTPS is not set # CONFIG_FEATURE_WGET_OPENSSL is not set # CONFIG_WHOIS is not set # CONFIG_ZCIP is not set # CONFIG_UDHCPD is not set # CONFIG_FEATURE_UDHCPD_BASE_IP_ON_MAC is not set # CONFIG_FEATURE_UDHCPD_WRITE_LEASES_EARLY is not set CONFIG_DHCPD_LEASES_FILE="" # CONFIG_DUMPLEASES is not set # CONFIG_DHCPRELAY is not set # CONFIG_UDHCPC is not set # CONFIG_FEATURE_UDHCPC_ARPING is not set # CONFIG_FEATURE_UDHCPC_SANITIZEOPT is not set CONFIG_UDHCPC_DEFAULT_SCRIPT="" CONFIG_UDHCPC6_DEFAULT_SCRIPT="" # CONFIG_UDHCPC6 is not set # CONFIG_FEATURE_UDHCPC6_RFC3646 is not set # CONFIG_FEATURE_UDHCPC6_RFC4704 is not set # CONFIG_FEATURE_UDHCPC6_RFC4833 is not set # CONFIG_FEATURE_UDHCPC6_RFC5970 is not set CONFIG_UDHCPC_DEFAULT_INTERFACE="" # CONFIG_FEATURE_UDHCP_PORT is not set CONFIG_UDHCP_DEBUG=0 CONFIG_UDHCPC_SLACK_FOR_BUGGY_SERVERS=0 # CONFIG_FEATURE_UDHCP_RFC3397 is not set # CONFIG_FEATURE_UDHCP_8021Q is not set CONFIG_IFUPDOWN_UDHCPC_CMD_OPTIONS="" # # Print Utilities # # CONFIG_LPD is not set # CONFIG_LPR is not set # CONFIG_LPQ is not set # # Mail Utilities # CONFIG_FEATURE_MIME_CHARSET="" # CONFIG_MAKEMIME is not set # CONFIG_POPMAILDIR is not set # CONFIG_FEATURE_POPMAILDIR_DELIVERY is not set # CONFIG_REFORMIME is not set # CONFIG_FEATURE_REFORMIME_COMPAT is not set # CONFIG_SENDMAIL is not set # # Process Utilities # # CONFIG_FEATURE_FAST_TOP is not set # CONFIG_FEATURE_SHOW_THREADS is not set # CONFIG_FREE is not set # CONFIG_FUSER is not set # CONFIG_IOSTAT is not set # CONFIG_KILL is not set # CONFIG_KILLALL is not set # CONFIG_KILLALL5 is not set # CONFIG_LSOF is not set # CONFIG_MPSTAT is not set # CONFIG_NMETER is not set # CONFIG_PGREP is not set # CONFIG_PKILL is not set # CONFIG_PIDOF is not set # CONFIG_FEATURE_PIDOF_SINGLE is not set # CONFIG_FEATURE_PIDOF_OMIT is not set # CONFIG_PMAP is not set # CONFIG_POWERTOP is not set # CONFIG_FEATURE_POWERTOP_INTERACTIVE is not set # CONFIG_PS is not set # CONFIG_FEATURE_PS_WIDE is not set # CONFIG_FEATURE_PS_LONG is not set # CONFIG_FEATURE_PS_TIME is not set # CONFIG_FEATURE_PS_UNUSUAL_SYSTEMS is not set # CONFIG_FEATURE_PS_ADDITIONAL_COLUMNS is not set # CONFIG_PSTREE is not set # CONFIG_PWDX is not set # CONFIG_SMEMCAP is not set # CONFIG_BB_SYSCTL is not set # CONFIG_TOP is not set # CONFIG_FEATURE_TOP_INTERACTIVE is not set # CONFIG_FEATURE_TOP_CPU_USAGE_PERCENTAGE is not set # CONFIG_FEATURE_TOP_CPU_GLOBAL_PERCENTS is not set # CONFIG_FEATURE_TOP_SMP_CPU is not set # CONFIG_FEATURE_TOP_DECIMALS is not set # CONFIG_FEATURE_TOP_SMP_PROCESS is not set # CONFIG_FEATURE_TOPMEM is not set # CONFIG_UPTIME is not set # CONFIG_FEATURE_UPTIME_UTMP_SUPPORT is not set # CONFIG_WATCH is not set # # Runit Utilities # # CONFIG_CHPST is not set # CONFIG_SETUIDGID is not set # CONFIG_ENVUIDGID is not set # CONFIG_ENVDIR is not set # CONFIG_SOFTLIMIT is not set # CONFIG_RUNSV is not set # CONFIG_RUNSVDIR is not set # CONFIG_FEATURE_RUNSVDIR_LOG is not set # CONFIG_SV is not set CONFIG_SV_DEFAULT_SERVICE_DIR="" # CONFIG_SVC is not set # CONFIG_SVOK is not set # CONFIG_SVLOGD is not set # CONFIG_CHCON is not set # CONFIG_GETENFORCE is not set # CONFIG_GETSEBOOL is not set # CONFIG_LOAD_POLICY is not set # CONFIG_MATCHPATHCON is not set # CONFIG_RUNCON is not set # CONFIG_SELINUXENABLED is not set # CONFIG_SESTATUS is not set # CONFIG_SETENFORCE is not set # CONFIG_SETFILES is not set # CONFIG_FEATURE_SETFILES_CHECK_OPTION is not set # CONFIG_RESTORECON is not set # CONFIG_SETSEBOOL is not set # # Shells # CONFIG_SH_IS_ASH=y # CONFIG_SH_IS_HUSH is not set # CONFIG_SH_IS_NONE is not set # CONFIG_BASH_IS_ASH is not set # CONFIG_BASH_IS_HUSH is not set CONFIG_BASH_IS_NONE=y CONFIG_SHELL_ASH=y CONFIG_ASH=y # CONFIG_ASH_OPTIMIZE_FOR_SIZE is not set CONFIG_ASH_INTERNAL_GLOB=y # CONFIG_ASH_BASH_COMPAT is not set # CONFIG_ASH_BASH_SOURCE_CURDIR is not set # CONFIG_ASH_BASH_NOT_FOUND_HOOK is not set # CONFIG_ASH_JOB_CONTROL is not set # CONFIG_ASH_ALIAS is not set # CONFIG_ASH_RANDOM_SUPPORT is not set # CONFIG_ASH_EXPAND_PRMT is not set # CONFIG_ASH_IDLE_TIMEOUT is not set # CONFIG_ASH_MAIL is not set CONFIG_ASH_ECHO=y # CONFIG_ASH_PRINTF is not set # CONFIG_ASH_TEST is not set # CONFIG_ASH_SLEEP is not set # CONFIG_ASH_HELP is not set # CONFIG_ASH_GETOPTS is not set # CONFIG_ASH_CMDCMD is not set # CONFIG_CTTYHACK is not set # CONFIG_HUSH is not set # CONFIG_SHELL_HUSH is not set # CONFIG_HUSH_BASH_COMPAT is not set # CONFIG_HUSH_BRACE_EXPANSION is not set # CONFIG_HUSH_BASH_SOURCE_CURDIR is not set # CONFIG_HUSH_LINENO_VAR is not set # CONFIG_HUSH_INTERACTIVE is not set # CONFIG_HUSH_SAVEHISTORY is not set # CONFIG_HUSH_JOB is not set # CONFIG_HUSH_TICK is not set # CONFIG_HUSH_IF is not set # CONFIG_HUSH_LOOPS is not set # CONFIG_HUSH_CASE is not set # CONFIG_HUSH_FUNCTIONS is not set # CONFIG_HUSH_LOCAL is not set # CONFIG_HUSH_RANDOM_SUPPORT is not set # CONFIG_HUSH_MODE_X is not set # CONFIG_HUSH_ECHO is not set # CONFIG_HUSH_PRINTF is not set # CONFIG_HUSH_TEST is not set # CONFIG_HUSH_HELP is not set # CONFIG_HUSH_EXPORT is not set # CONFIG_HUSH_EXPORT_N is not set # CONFIG_HUSH_READONLY is not set # CONFIG_HUSH_KILL is not set # CONFIG_HUSH_WAIT is not set # CONFIG_HUSH_COMMAND is not set # CONFIG_HUSH_TRAP is not set # CONFIG_HUSH_TYPE is not set # CONFIG_HUSH_TIMES is not set # CONFIG_HUSH_READ is not set # CONFIG_HUSH_SET is not set # CONFIG_HUSH_UNSET is not set # CONFIG_HUSH_ULIMIT is not set # CONFIG_HUSH_UMASK is not set # CONFIG_HUSH_GETOPTS is not set # CONFIG_HUSH_MEMLEAK is not set # # Options common to all shells # # CONFIG_FEATURE_SH_MATH is not set # CONFIG_FEATURE_SH_MATH_64 is not set # CONFIG_FEATURE_SH_MATH_BASE is not set # CONFIG_FEATURE_SH_EXTRA_QUIET is not set # CONFIG_FEATURE_SH_STANDALONE is not set CONFIG_FEATURE_SH_NOFORK=y # CONFIG_FEATURE_SH_READ_FRAC is not set # CONFIG_FEATURE_SH_HISTFILESIZE is not set CONFIG_FEATURE_SH_EMBEDDED_SCRIPTS=y # # System Logging Utilities # # CONFIG_KLOGD is not set # CONFIG_FEATURE_KLOGD_KLOGCTL is not set # CONFIG_LOGGER is not set # CONFIG_LOGREAD is not set # CONFIG_FEATURE_LOGREAD_REDUCED_LOCKING is not set # CONFIG_SYSLOGD is not set # CONFIG_FEATURE_ROTATE_LOGFILE is not set # CONFIG_FEATURE_REMOTE_LOG is not set # CONFIG_FEATURE_SYSLOGD_DUP is not set # CONFIG_FEATURE_SYSLOGD_CFG is not set # CONFIG_FEATURE_SYSLOGD_PRECISE_TIMESTAMPS is not set CONFIG_FEATURE_SYSLOGD_READ_BUFFER_SIZE=0 # CONFIG_FEATURE_IPC_SYSLOG is not set CONFIG_FEATURE_IPC_SYSLOG_BUFFER_SIZE=0 # CONFIG_FEATURE_KMSG_SYSLOG is not set ================================================ FILE: rust-toolchain.toml ================================================ [toolchain] channel = "nightly" targets = ["x86_64-unknown-none"] components = ["rust-src"] ================================================ FILE: src/arch/mod.rs ================================================ pub mod x86_64; pub use self::x86_64::*; ================================================ FILE: src/arch/x86_64/cpu_local.rs ================================================ use x86::msr::{rdmsr, IA32_GS_BASE}; use x86_64::structures::{gdt::GlobalDescriptorTable, tss::TaskStateSegment}; pub struct CpuLocalData { pub kernel_sp: usize, pub gdt: GlobalDescriptorTable, } #[repr(C, packed)] pub struct Kpcr { pub tss: TaskStateSegment, pub cpu_local: &'static mut CpuLocalData, pub user_rsp0_tmp: usize, } pub fn get_kpcr() -> &'static mut Kpcr { unsafe { &mut *(rdmsr(IA32_GS_BASE) as *mut _) } } pub fn get_tss() -> &'static mut TaskStateSegment { unsafe { &mut *(rdmsr(IA32_GS_BASE) as *mut _) } } ================================================ FILE: src/arch/x86_64/gdt.rs ================================================ use core::alloc::Layout; use alloc::alloc::alloc_zeroed; use lazy_static::lazy_static; use x86::msr::{wrmsr, IA32_GS_BASE}; use x86_64::{ instructions::tables::load_tss, registers::segmentation::{Segment, CS, DS, ES, FS, GS, SS}, structures::{ gdt::{Descriptor, GlobalDescriptorTable, SegmentSelector}, tss::TaskStateSegment, }, }; use crate::mem::consts::KERNEL_STACK_SIZE; use super::cpu_local::{get_kpcr, get_tss, CpuLocalData, Kpcr}; pub const KERNEL_CS_IDX: u16 = 1; pub const KERNEL_DS_IDX: u16 = 2; pub const TSS_IDX: u16 = 3; pub const USER_DS_IDX: u16 = 5; pub const USER_CS_IDX: u16 = 6; static mut STACK: [u8; KERNEL_STACK_SIZE] = [0; KERNEL_STACK_SIZE]; lazy_static! { static ref BOOT_GDT: (GlobalDescriptorTable, [SegmentSelector; 2]) = { let mut gdt = GlobalDescriptorTable::new(); let kernel_code_sel = gdt.append(Descriptor::kernel_code_segment()); let kernel_data_sel = gdt.append(Descriptor::kernel_data_segment()); (gdt, [kernel_code_sel, kernel_data_sel]) }; } pub fn init_boot() { unsafe { BOOT_GDT.0.load(); CS::set_reg(BOOT_GDT.1[0]); DS::set_reg(BOOT_GDT.1[1]); ES::set_reg(BOOT_GDT.1[1]); FS::set_reg(BOOT_GDT.1[1]); GS::set_reg(BOOT_GDT.1[1]); SS::set_reg(BOOT_GDT.1[1]); } } pub fn init() { unsafe { let kpcr_layout = Layout::new::(); let kpcr_ptr = alloc_zeroed(kpcr_layout) as *mut Kpcr; wrmsr(IA32_GS_BASE, kpcr_ptr as u64); let tls_layout = Layout::new::(); let tls_ptr = alloc_zeroed(tls_layout) as *mut CpuLocalData; get_kpcr().cpu_local = &mut *tls_ptr; } let tss = get_tss(); *tss = TaskStateSegment::new(); tss.privilege_stack_table[0] = x86_64::VirtAddr::new( unsafe { #[allow(static_mut_refs)] STACK.as_mut_ptr() } as u64 + KERNEL_STACK_SIZE as u64, ); let gdt = &mut get_kpcr().cpu_local.gdt; *gdt = GlobalDescriptorTable::new(); // kernel code let kernel_cs_sel = gdt.append(Descriptor::kernel_code_segment()); // kernel data let kernel_ds_sel = gdt.append(Descriptor::kernel_data_segment()); // TSS let tss_sel = gdt.append(Descriptor::tss_segment(tss)); // user data (syscall) let _user_ds_sel = gdt.append(Descriptor::user_data_segment()); // user code let _user_cs_sel = gdt.append(Descriptor::user_code_segment()); gdt.load(); unsafe { CS::set_reg(kernel_cs_sel); DS::set_reg(kernel_ds_sel); ES::set_reg(kernel_ds_sel); SS::set_reg(kernel_ds_sel); // FS::set_reg(kernel_ds_sel); // GS::set_reg(kernel_ds_sel); load_tss(tss_sel); } } ================================================ FILE: src/arch/x86_64/idt.rs ================================================ use lazy_static::lazy_static; use pc_keyboard::{DecodedKey, HandleControl, Keyboard, ScancodeSet1, layouts::Us104Key}; use pic8259::ChainedPics; use spin::Mutex; use x86::{ io::outb, msr::{IA32_FS_BASE, rdmsr}, }; use x86_64::{ instructions::port::Port, registers::control::Cr3, structures::idt::{InterruptDescriptorTable, PageFaultErrorCode}, }; use crate::{ backtrace, fs::devfs::{input::KBD_DEVICE, tty::TTY}, mem::addr::VirtAddr, task::get_scheduler, util::IrqMutex, }; pub const PIC_1_OFFSET: u8 = 32; pub const PIC_2_OFFSET: u8 = PIC_1_OFFSET + 8; pub static PICS: Mutex = Mutex::new(unsafe { ChainedPics::new(PIC_1_OFFSET, PIC_2_OFFSET) }); pub const TIMER_IRQ: u8 = PIC_1_OFFSET; pub const KEYBOARD_IRQ: u8 = PIC_1_OFFSET + 1; pub const COM2_IRQ: u8 = PIC_1_OFFSET + 3; lazy_static! { pub static ref IDT: InterruptDescriptorTable = { let mut idt = InterruptDescriptorTable::new(); #[allow(clippy::fn_to_numeric_cast)] unsafe { idt.divide_error.set_handler_addr(x86_64::VirtAddr::new(divide_error_handler as u64)); idt.debug.set_handler_addr(x86_64::VirtAddr::new(debug_handler as u64)); idt.non_maskable_interrupt.set_handler_addr(x86_64::VirtAddr::new(nmi_handler as u64)); idt.breakpoint.set_handler_addr(x86_64::VirtAddr::new(breakpoint_handler as u64)); idt.overflow.set_handler_addr(x86_64::VirtAddr::new(overflow_handler as u64)); idt.bound_range_exceeded .set_handler_addr(x86_64::VirtAddr::new(bound_range_exceeded_handler as u64)); idt.invalid_opcode.set_handler_addr(x86_64::VirtAddr::new(invalid_opcode_handler as u64)); idt.device_not_available .set_handler_addr(x86_64::VirtAddr::new(device_not_available_handler as u64)); idt.double_fault .set_handler_addr(x86_64::VirtAddr::new(double_fault_handler as u64)) .set_stack_index(0); // reserved: 0x09 coprocessor segment overrun exception idt.invalid_tss.set_handler_addr(x86_64::VirtAddr::new(invalid_tss_handler as u64)); idt.segment_not_present .set_handler_addr(x86_64::VirtAddr::new(segment_not_present_handler as u64)); idt.stack_segment_fault .set_handler_addr(x86_64::VirtAddr::new(stack_segment_fault_handler as u64)); idt.general_protection_fault .set_handler_addr(x86_64::VirtAddr::new(general_protection_fault_handler as u64)); idt.page_fault.set_handler_addr(x86_64::VirtAddr::new(page_fault_handler as u64)); // reserved: 0x0F idt.x87_floating_point .set_handler_addr(x86_64::VirtAddr::new(x87_floating_point_handler as u64)); idt.alignment_check.set_handler_addr(x86_64::VirtAddr::new(alignment_check_handler as u64)); idt.machine_check.set_handler_addr(x86_64::VirtAddr::new(machine_check_handler as u64)); idt.simd_floating_point .set_handler_addr(x86_64::VirtAddr::new(simd_floating_point_handler as u64)); idt.virtualization.set_handler_addr(x86_64::VirtAddr::new(virtualization_handler as u64)); // reserved: 0x15 - 0x1C idt.vmm_communication_exception .set_handler_addr(x86_64::VirtAddr::new(vmm_communication_exception_handler as u64)); idt.security_exception .set_handler_addr(x86_64::VirtAddr::new(security_exception_handler as u64)); idt[TIMER_IRQ].set_handler_addr(x86_64::VirtAddr::new(timer_handler as u64)); idt[KEYBOARD_IRQ].set_handler_addr(x86_64::VirtAddr::new(keyboard_handler as u64)); idt[COM2_IRQ].set_handler_addr(x86_64::VirtAddr::new(com2_handler as u64)); } idt }; } #[derive(Copy, Clone, Debug, Default)] #[repr(C, packed)] pub struct InterruptFrame { pub r15: usize, pub r14: usize, pub r13: usize, pub r12: usize, pub rbp: usize, pub rbx: usize, pub r11: usize, pub r10: usize, pub r9: usize, pub r8: usize, pub rsi: usize, pub rdi: usize, pub rdx: usize, pub rcx: usize, pub rax: usize, pub rip: usize, pub cs: usize, pub rflags: usize, pub rsp: usize, pub ss: usize, } impl InterruptFrame { #[inline(always)] pub fn is_user_mode(&self) -> bool { self.cs & 0x3 != 0 } } #[derive(Copy, Clone, Debug, Default)] #[repr(C, packed)] pub struct InterruptErrorFrame { pub code: usize, pub frame: InterruptFrame, } pub fn notify_eoi(index: u8) { unsafe { PICS.lock().notify_end_of_interrupt(index) } } macro_rules! interrupt_handler { ($name:ident, $num:literal, $push_error:expr) => { #[unsafe(naked)] unsafe extern "C" fn $name() { { core::arch::naked_asm!(concat!( $push_error, " test qword ptr [rsp + 16], 0x3 jz 2f swapgs 2: xchg [rsp], rax ", crate::push_regs!()," push rax mov rdi, {num} mov rsi, rsp call x64_handle_interrupt add rsp, 8 ", crate::pop_regs!()," test qword ptr [rsp + 8], 0x3 jz 3f swapgs 3: iretq " ), num = const($num)) } } }; } macro_rules! has_error { () => { "" }; } macro_rules! no_error { () => { "push 0" }; } interrupt_handler!(divide_error_handler, 0x0, no_error!()); interrupt_handler!(debug_handler, 0x1, no_error!()); interrupt_handler!(nmi_handler, 0x2, no_error!()); interrupt_handler!(breakpoint_handler, 0x3, no_error!()); interrupt_handler!(overflow_handler, 0x4, no_error!()); interrupt_handler!(bound_range_exceeded_handler, 0x5, no_error!()); interrupt_handler!(invalid_opcode_handler, 0x6, no_error!()); interrupt_handler!(device_not_available_handler, 0x7, no_error!()); interrupt_handler!(double_fault_handler, 0x8, has_error!()); interrupt_handler!(invalid_tss_handler, 0xA, has_error!()); interrupt_handler!(segment_not_present_handler, 0xB, has_error!()); interrupt_handler!(stack_segment_fault_handler, 0xC, has_error!()); interrupt_handler!(general_protection_fault_handler, 0xD, has_error!()); interrupt_handler!(page_fault_handler, 0xE, has_error!()); interrupt_handler!(x87_floating_point_handler, 0x10, no_error!()); interrupt_handler!(alignment_check_handler, 0x11, has_error!()); interrupt_handler!(machine_check_handler, 0x12, no_error!()); interrupt_handler!(simd_floating_point_handler, 0x13, no_error!()); interrupt_handler!(virtualization_handler, 0x14, no_error!()); interrupt_handler!(vmm_communication_exception_handler, 0x1D, has_error!()); interrupt_handler!(security_exception_handler, 0x1E, has_error!()); interrupt_handler!(timer_handler, 32, no_error!()); interrupt_handler!(keyboard_handler, 33, no_error!()); interrupt_handler!(com2_handler, 35, no_error!()); use x86::irq::*; #[unsafe(no_mangle)] extern "C" fn x64_handle_interrupt(vector: u8, stack_frame: *mut InterruptErrorFrame) { let stack_frame = unsafe { &mut *stack_frame }; let error_code = stack_frame.code; match vector { TIMER_IRQ => { super::time::pit_irq(); let sched = get_scheduler(); notify_eoi(TIMER_IRQ); sched.preempt(); } KEYBOARD_IRQ => { do_keyboard_input(); notify_eoi(KEYBOARD_IRQ); } COM2_IRQ => { notify_eoi(COM2_IRQ); } DIVIDE_ERROR_VECTOR => { log::error!("\nEXCEPTION: DIVIDE ERROR\n{:#x?}", stack_frame); panic!("Divide error"); } DEBUG_VECTOR => { log::error!("\nEXCEPTION: DEBUG EXCEPTION\n{:#x?}", stack_frame); } NONMASKABLE_INTERRUPT_VECTOR => { log::error!("\nEXCEPTION: NON-MASKABLE INTERRUPT\n{:#x?}", stack_frame); panic!("Non-maskable interrupt"); } OVERFLOW_VECTOR => { log::error!("\nEXCEPTION: OVERFLOW\n{:#x?}", stack_frame); panic!("Overflow"); } BOUND_RANGE_EXCEEDED_VECTOR => { log::error!("\nEXCEPTION: BOUND RANGE EXCEEDED\n{:#x?}", stack_frame); panic!("Bound range exceeded"); } INVALID_OPCODE_VECTOR => { log::error!("\nEXCEPTION: INVALID OPCODE\n{:#x?}", stack_frame); panic!("Invalid opcode"); } DEVICE_NOT_AVAILABLE_VECTOR => { log::error!("\nEXCEPTION: DEVICE NOT AVAILABLE\n{:#x?}", stack_frame); panic!("Device not available"); } DOUBLE_FAULT_VECTOR => { log::error!( "\nEXCEPTION: DOUBLE FAULT\n{:#x?}\nError code: {:#b}\ncr3: {:#x}", stack_frame, error_code, Cr3::read_raw().0.start_address().as_u64() ); panic!("Double fault"); } INVALID_TSS_VECTOR => { log::error!( "\nEXCEPTION: INVALID TSS\n{:#x?}\nError code: {:#b}", stack_frame, error_code ); panic!("Invalid TSS"); } SEGMENT_NOT_PRESENT_VECTOR => { log::error!( "\nEXCEPTION: SEGMENT NOT PRESENT\n{:#x?}\nError code: {:#b}", stack_frame, error_code ); panic!("Segment not present"); } STACK_SEGEMENT_FAULT_VECTOR => { log::error!( "\nEXCEPTION: STACK SEGMENT FAULT\n{:#x?}\nError code: {:#b}", stack_frame, error_code ); panic!("Stack segment fault"); } GENERAL_PROTECTION_FAULT_VECTOR => { log::error!( "\nEXCEPTION: GENERAL PROTECTION FAULT\n{:#x?}\nError code: {:#b}", stack_frame, error_code ); unsafe { let fsbase = rdmsr(IA32_FS_BASE); log::debug!("FSBASE: {:#x}", fsbase); } if stack_frame.frame.is_user_mode() { backtrace::unwind_user_stack_from(stack_frame.frame.rbp, stack_frame.frame.rip); get_scheduler().exit_current(1); } panic!("General Protection Fault"); } PAGE_FAULT_VECTOR => { let accessed_address = x86_64::registers::control::Cr2::read_raw(); let cr3 = x86_64::registers::control::Cr3::read_raw().0; let error_code = PageFaultErrorCode::from_bits_truncate(error_code as u64); let current = get_scheduler().current_task_opt(); if let Some(current) = current { if let Err(e) = current.handle_page_fault( unsafe { VirtAddr::new_unchecked(accessed_address as usize) }, *stack_frame, error_code, ) { log::error!( "\nEXCEPTION: USER PAGE FAULT while accessing {:#x}\n\ error code: {:?}\ncr3: {:#x}\n{:#x?}", accessed_address, error_code, cr3.start_address().as_u64(), stack_frame, ); let rip = stack_frame.frame.rip; log::error!("Exception IP {:#x}", rip); log::error!("Faulted access address {:#x}", accessed_address); log::error!("Error: {:?}", e); panic!("User page fault"); } } else { log::error!( "\nEXCEPTION: KERNEL PAGE FAULT while accessing {:#x}\n\ error code: {:?}\ncr3: {:#x}\n{:#x?}", accessed_address, error_code, cr3.start_address().as_u64(), stack_frame, ); let rip = stack_frame.frame.rip; log::error!("Exception IP {:#x}", rip); log::error!("Faulted access address {:#x}", accessed_address,); panic!("Kernel page fault"); } } X87_FPU_VECTOR => { log::error!("\nEXCEPTION: x87 FLOATING POINT\n{:#x?}", stack_frame); panic!("x87 floating point exception"); } ALIGNMENT_CHECK_VECTOR => { log::error!( "\nEXCEPTION: ALIGNMENT CHECK\n{:#x?}\nError code: {:#b}", stack_frame, error_code ); panic!("Alignment check"); } MACHINE_CHECK_VECTOR => { log::error!("\nEXCEPTION: MACHINE CHECK\n{:#x?}", stack_frame); panic!() } SIMD_FLOATING_POINT_VECTOR => { log::error!("\nEXCEPTION: SIMD FLOATING POINT\n{:#x?}", stack_frame); panic!("SIMD floating point exception"); } VIRTUALIZATION_VECTOR => { log::error!("\nEXCEPTION: VIRTUALIZATION\n{:#x?}", stack_frame); panic!("Virtualization exception"); } 0x1D => { log::error!( "\nEXCEPTION: VMM COMMUNICATION EXCEPTION\n{:#x?}\nError code: {:#b}", stack_frame, error_code ); panic!("VMM communication exception"); } 0x1E => { log::error!( "\nEXCEPTION: SECURITY EXCEPTION\n{:#x?}\nError code: {:#b}", stack_frame, error_code ); panic!("Security exception"); } _ => log::warn!("Unhandled interrupt: {}", vector), } } pub const PIC_1_DATA_PORT: u8 = 0x21; pub const PIC_2_DATA_PORT: u8 = 0xa1; pub fn mask_irq(irq: u8) { let irq = irq - PIC_1_OFFSET; let port = if irq < 8 { PIC_1_DATA_PORT } else { PIC_2_DATA_PORT }; let irq = if irq < 8 { irq } else { irq - 8 }; let val = unsafe { x86::io::inb(port as u16) } | (1 << irq); unsafe { outb(port as u16, val) }; } pub fn unmask_irq(irq: u8) { let irq = irq - PIC_1_OFFSET; let port = if irq < 8 { PIC_1_DATA_PORT } else { PIC_2_DATA_PORT }; let irq = if irq < 8 { irq } else { irq - 8 }; let val = unsafe { x86::io::inb(port as u16) } & !(1 << irq); unsafe { outb(port as u16, val) }; } pub fn init() { IDT.load(); unsafe { PICS.lock().initialize(); } unmask_irq(TIMER_IRQ); unmask_irq(KEYBOARD_IRQ); unmask_irq(COM2_IRQ); } fn do_keyboard_input() { lazy_static! { static ref KEYBOARD: IrqMutex> = IrqMutex::new( Keyboard::new(ScancodeSet1::new(), Us104Key, HandleControl::Ignore) ); } let mut port = Port::new(0x60); let scancode: u8 = unsafe { port.read() }; let mut keyboard = KEYBOARD.lock(); if let Ok(Some(key_evt)) = keyboard.add_byte(scancode) { if let Some(kbd) = KBD_DEVICE.get() { kbd.handle_kbd_irq(&key_evt); } if let Some(key) = keyboard.process_keyevent(key_evt) { match key { DecodedKey::Unicode(c) => { TTY.get().unwrap().input_char(c as u8); } DecodedKey::RawKey(_code) => {} } } } } ================================================ FILE: src/arch/x86_64/mod.rs ================================================ use alloc::sync::Arc; use limine::request::{ BootTimeRequest, FramebufferRequest, HhdmRequest, KernelFileRequest, MemoryMapRequest, StackSizeRequest, }; use spin::Once; use x86::{ controlregs::{self, Cr0, Cr4}, cpuid::{CpuId, FeatureInfo}, }; use x86_64::instructions::interrupts; use crate::{ backtrace, fs::{ self, initramfs::get_root, opened_file::{OpenFlags, OpenedFile}, path::Path, }, god_mode::GOD_MODE_FIFO, graphics, mem::{ self, allocator::{KERNEL_FRAME_ALLOCATOR, KERNEL_PAGE_ALLOCATOR}, consts::KERNEL_STACK_SIZE, }, serial::serial1_recv, task::{current_task, get_scheduler, Task}, }; pub mod cpu_local; pub mod gdt; pub mod idt; pub mod syscall; pub mod task; pub mod time; static HHDM: HhdmRequest = HhdmRequest::new(); static _STACK: StackSizeRequest = StackSizeRequest::new().with_size(KERNEL_STACK_SIZE as u64); static BOOT_TIME: BootTimeRequest = BootTimeRequest::new(); static FB_REQUEST: FramebufferRequest = FramebufferRequest::new(); static MEM_MAP: MemoryMapRequest = MemoryMapRequest::new(); static KERNEL_FILE: KernelFileRequest = KernelFileRequest::new(); pub static CPUID_FEATURE_INFO: Once = Once::new(); pub fn get_cpuid_feature_info() -> &'static FeatureInfo { CPUID_FEATURE_INFO.call_once(|| { CpuId::new() .get_feature_info() .expect("Error getting CPUID feature info") }) } pub fn arch_main() { interrupts::disable(); let kernel_file = KERNEL_FILE .get_response() .expect("Error getting kernel binary from Limine"); let kernel_file = kernel_file.file(); let kernel_file_base = kernel_file.addr(); let kernel_file_len = kernel_file.size() as usize; let kernel_file_data = unsafe { core::slice::from_raw_parts(kernel_file_base, kernel_file_len) }; backtrace::KERNEL_ELF.call_once(|| { xmas_elf::ElfFile::new(kernel_file_data).expect("Error parsing kernel ELF file data") }); crate::PHYSICAL_OFFSET.call_once(|| { HHDM.get_response() .expect("Error getting HHDM response from Limine") .offset() as usize }); let memmap = MEM_MAP .get_response() .expect("Error getting memory map response from Limine") .entries(); crate::logging::init(); log::info!("Logger initialized."); log::info!("Setting up time structures."); let boot_time = BOOT_TIME .get_response() .expect("Error getting boot time response from Limine") .boot_time(); time::init(boot_time.as_secs() as i64); log::info!("Initializing FPU mechanisms."); let features = get_cpuid_feature_info(); assert!(features.has_fxsave_fxstor(), "FXSAVE/FXRSTOR not available"); assert!(features.has_mmx(), "MMX not available"); assert!(features.has_fpu(), "FPU not available"); assert!(features.has_sse(), "SSE not available"); unsafe { // enable FXSAVE and FXRSTOR controlregs::cr4_write(controlregs::cr4() | Cr4::CR4_ENABLE_SSE | Cr4::CR4_UNMASKED_SSE); log::trace!("CR4_ENABLE_SSE and CR4_UNMASKED_SSE set."); // controlregs::xcr0_write( // controlregs::xcr0() // | Xcr0::XCR0_SSE_STATE // | Xcr0::XCR0_FPU_MMX_STATE // | Xcr0::XCR0_AVX_STATE, // ); // log::trace!("XCR0_SSE_STATE, XCR0_FPU_MMX_STATE, and XCR0_AVX_STATE set."); controlregs::cr0_write(controlregs::cr0() & !Cr0::CR0_EMULATE_COPROCESSOR); log::trace!("CR0_EMULATE_COPROCESSOR cleared."); controlregs::cr0_write(controlregs::cr0() | Cr0::CR0_MONITOR_COPROCESSOR); log::trace!("CR0_MONITOR_COPROCESSOR set."); } log::info!("Initializing boot GDT."); gdt::init_boot(); // let fb_tag = boot_info.framebuffer_tag().expect("No multiboot2 framebuffer tag found"); let fb_resp = FB_REQUEST .get_response() .expect("Error getting framebuffer response from Limine"); log::info!("Initializing kernel frame and page allocators."); mem::allocator::init(memmap).expect("Error initializing kernel frame and page allocators"); log::info!("Remapping kernel to new page table."); let kernel_addr_space = mem::remap_kernel().expect("Error remapping kernel"); log::info!("Setting up kernel heap."); let _heap_mp = kernel_addr_space .lock() .with_mapper(|mut mapper| mem::init_heap(&mut mapper).expect("Error setting up heap")); log::info!("Converting kernel frame and page allocators to use heap."); { KERNEL_FRAME_ALLOCATOR .get() .expect("Error getting kernel frame allocator") .lock() .convert_to_heap_allocated(); KERNEL_PAGE_ALLOCATOR .get() .expect("Error getting kernel page allocator") .lock() .convert_to_heap_allocated(); } log::info!("Initializing VGA graphics."); graphics::init(fb_resp).expect("Error initializing VGA graphics"); log::info!("Setting up syscalls."); unsafe { syscall::init(); } log::info!("Loading GDT."); gdt::init(); log::info!("Loading IDT."); idt::init(); log::info!("Initializing filesystems."); fs::initramfs::init().expect("Error initializing initramfs"); log::info!("Initializing task scheduler."); crate::task::init(); log::info!("Starting init process."); let sched = get_scheduler(); fs::devfs::init(); log::info!("Welcome to K4DOS!"); { let task = Task::new_kernel(sched, poll_serial1, true); sched.push_runnable(task, true); } // god_mode::init(); loop { interrupts::enable_and_hlt(); } } pub fn startup_init() { let exe = "/bin/sh"; let file = get_root() .unwrap() .lookup(Path::new(exe), true) .unwrap() .as_file() .unwrap() .clone(); let current = current_task(); let mut files = current.opened_files.lock(); let console = get_root() .unwrap() .lookup_path(Path::new("/dev/tty"), true) .unwrap(); // stdin files .open_with_fd( 0, Arc::new(OpenedFile::new(console.clone(), OpenFlags::O_RDONLY, 0)), OpenFlags::O_RDONLY | OpenFlags::O_CLOEXEC, ) .unwrap(); // stdout files .open_with_fd( 1, Arc::new(OpenedFile::new(console.clone(), OpenFlags::O_WRONLY, 0)), OpenFlags::O_WRONLY | OpenFlags::O_CLOEXEC, ) .unwrap(); // stderr files .open_with_fd( 2, Arc::new(OpenedFile::new(console, OpenFlags::O_WRONLY, 0)), OpenFlags::O_WRONLY | OpenFlags::O_CLOEXEC, ) .unwrap(); drop(files); current .exec(file, &[exe.as_bytes()], &[b"FOO=bar"]) .unwrap(); } fn poll_serial1() { loop { let c = serial1_recv(); if let Some(c) = c { // TTY.get().unwrap().input_char(c); loop { if let Ok(mut lock) = GOD_MODE_FIFO.get().unwrap().try_lock() { lock.push_back(c); drop(lock); break; } // sched.preempt(); interrupts::enable_and_hlt(); } } interrupts::enable_and_hlt(); } } pub fn idle() { loop { interrupts::enable_and_hlt(); } } ================================================ FILE: src/arch/x86_64/syscall.rs ================================================ use core::mem::offset_of; use x86::msr::{rdmsr, wrmsr}; use crate::mem::kernel_addr_space_scope; use crate::userland::syscall::{ QUIET_SYSCALLS, SyscallHandler, errno_to_isize, syscall_name_by_number, }; use super::gdt::{KERNEL_CS_IDX, USER_DS_IDX}; use super::idt::InterruptFrame; #[macro_export] macro_rules! push_regs { () => { " // push scratch regs push rcx push rdx push rdi push rsi push r8 push r9 push r10 push r11 // push preserved regs push rbx push rbp push r12 push r13 push r14 push r15 " }; } #[macro_export] macro_rules! pop_regs { () => { " // pop preserved regs pop r15 pop r14 pop r13 pop r12 pop rbp pop rbx // pop scratch regs pop r11 pop r10 pop r9 pop r8 pop rsi pop rdi pop rdx pop rcx pop rax " }; } #[unsafe(naked)] pub unsafe extern "C" fn syscall_entry() { use x86_64::structures::tss::TaskStateSegment; { core::arch::naked_asm!( concat!( " cli swapgs mov gs:[{off} + {sp}], rsp mov rsp, gs:[{off} + {ksp}] push qword ptr {ss_sel} push qword ptr gs:[{off} + {sp}] push r11 push qword ptr {cs_sel} push rcx push rax ", push_regs!(), " mov rdi, rsp cld call x64_handle_syscall cli ", pop_regs!(), " test dword ptr [rsp + 4], 0xFFFF8000 jnz 2f pop rcx add rsp, 8 pop r11 pop qword ptr gs:[{off} + {sp}] mov rsp, gs:[{off} + {sp}] // pop rsp cli swapgs sysretq 2: xor rcx, rcx xor r11, r11 cli swapgs iretq " ), off = const(0), sp = const(offset_of!(crate::arch::cpu_local::Kpcr, user_rsp0_tmp)), ksp = const(offset_of!(TaskStateSegment, privilege_stack_table)), ss_sel = const((crate::arch::gdt::USER_DS_IDX << 3) | 3), cs_sel = const((crate::arch::gdt::USER_CS_IDX << 3) | 3), ) } } #[unsafe(no_mangle)] unsafe extern "C" fn x64_handle_syscall(ctx: *mut InterruptFrame) -> isize { let context = unsafe { core::ptr::read(ctx) }; handle_syscall( context.rdi, context.rsi, context.rdx, context.r10, context.r8, context.r9, context.rax, ctx, ) } #[allow(clippy::too_many_arguments)] fn handle_syscall( a1: usize, a2: usize, a3: usize, a4: usize, a5: usize, a6: usize, n: usize, frame: *mut InterruptFrame, ) -> isize { let mut handler = SyscallHandler { frame: unsafe { &mut *frame }, }; let guard = kernel_addr_space_scope().unwrap(); let res = handler.dispatch(a1, a2, a3, a4, a5, a6, n); drop(guard); if let Err(ref err) = res { if !QUIET_SYSCALLS.contains(&n) { log::error!( "Syscall handler for `{}` returned Err: {}", syscall_name_by_number(n), err ); } } let retval = errno_to_isize(&res); handler.frame.rax = retval as usize; retval } /// # Safety /// This writes several MSR registers. pub unsafe fn init() { let kernel_cs_offset = (KERNEL_CS_IDX as u64) << 3; let user_ds_offset = (USER_DS_IDX as u64) << 3; let mut star = 0u64; star |= (user_ds_offset - 8) << 48; star |= kernel_cs_offset << 32; unsafe { wrmsr(x86::msr::IA32_STAR, star); wrmsr(x86::msr::IA32_LSTAR, syscall_entry as *const u8 as u64); wrmsr(x86::msr::IA32_FMASK, 0x200); wrmsr(x86::msr::IA32_CSTAR, 0); wrmsr(x86::msr::IA32_EFER, rdmsr(x86::msr::IA32_EFER) | 1); } } ================================================ FILE: src/arch/x86_64/task.rs ================================================ use core::{alloc::Layout, ptr::Unique, slice::SlicePattern}; use alloc::{alloc::alloc_zeroed, boxed::Box, vec::Vec}; use x86::{ cpuid::CpuId, msr::{IA32_FS_BASE, IA32_GS_BASE, rdmsr, wrmsr}, tlb, }; use x86_64::instructions::interrupts; use crate::{ fs::FileRef, mem::{ addr::VirtAddr, addr_space::AddressSpace, allocator::alloc_kernel_frames, consts::{KERNEL_STACK_SIZE, PAGE_SIZE, USER_STACK_BOTTOM, USER_STACK_TOP}, }, task::{ signal::Signal, vmem::{MMapFlags, MMapKind, MMapProt, Vmem}, }, userland::elf::{self, AuxvType, SymTabEntry}, util::{KResult, stack::Stack}, }; use super::{ cpu_local::get_tss, gdt::{KERNEL_CS_IDX, KERNEL_DS_IDX, USER_DS_IDX}, idt::{InterruptErrorFrame, InterruptFrame}, }; fn fxsave(fpu: &mut Box<[u8]>) { unsafe { core::arch::asm!("fxsave [{}]", in(reg) (**fpu).as_ptr(), in("rax") u64::MAX, in("rdx") u64::MAX) } } fn fxrstor(fpu: &mut Box<[u8]>) { unsafe { core::arch::asm!("fxrstor [{}]", in(reg) (**fpu).as_ptr(), in("rax") u64::MAX, in("rdx") u64::MAX) } } pub fn arch_context_switch(prev: &mut ArchTask, next: &mut ArchTask) { unsafe { // prev.fsbase = VirtAddr::new(rdmsr(IA32_FS_BASE) as usize); // prev.gsbase = VirtAddr::new(rdmsr(IA32_GS_BASE) as usize); wrmsr(IA32_FS_BASE, next.fsbase.value() as u64); // swapgs(); wrmsr(IA32_GS_BASE, next.gsbase.value() as u64); get_tss().privilege_stack_table[0] = x86_64::VirtAddr::new( (next.kernel_stack.as_ptr() as usize + next.kernel_stack.len()) as u64, ); // swapgs(); if let Some(fpu) = prev.fpu_storage.as_mut() { fxsave(fpu); } if let Some(fpu) = next.fpu_storage.as_mut() { fxrstor(fpu) } next.address_space.switch(); // interrupts::disable(); // why doesn't this work instead of the FIXME in fork()? context_switch(&mut prev.context, next.context.as_ref()) } } #[unsafe(naked)] unsafe extern "C" fn iretq_init() -> ! { { core::arch::naked_asm!( " cli add rsp, 8 ", crate::pop_regs!(), " iretq ", ) } } #[unsafe(naked)] unsafe extern "C" fn fork_init() -> ! { { core::arch::naked_asm!(concat!( " cli add rsp, 8 ", crate::pop_regs!(), " swapgs iretq " ),) } } #[unsafe(naked)] unsafe extern "C" fn context_switch(_prev: &mut Unique, _next: &Context) { { core::arch::naked_asm!( " pushfq push rbp push rbx push r12 push r13 push r14 push r15 mov [rdi], rsp mov rsp, rsi pop r15 pop r14 pop r13 pop r12 pop rbx pop rbp popfq ret ", ) } } #[derive(Clone, Debug, Default)] #[repr(C)] pub struct Context { r15: usize, r14: usize, r13: usize, r12: usize, rbx: usize, rbp: usize, rflags: usize, rip: usize, } #[unsafe(naked)] unsafe extern "C" fn exec_entry(rcx: usize, rsp: usize, r11: usize) -> ! { { core::arch::naked_asm!( " cli swapgs mov r11, rdx mov rcx, rdi mov rsp, rsi mov r15, {user_ds} mov ds, r15d mov es, r15d mov fs, r15d mov gs, r15d xor rax, rax xor rbx, rbx xor rdx, rdx xor rsi, rsi xor rbp, rbp xor r8, r8 xor r9, r9 xor r10, r10 xor r12, r12 xor r13, r13 xor r14, r14 xor r15, r15 sysretq ", user_ds = const(((USER_DS_IDX as u64) << 3) | 3), ) } } #[repr(C)] pub struct ArchTask { context: Unique, kernel_stack: Box<[u8]>, user: bool, pub(crate) address_space: AddressSpace, fsbase: VirtAddr, gsbase: VirtAddr, fpu_storage: Option>, pub symtab: Option>, } impl ArchTask { pub fn new_idle() -> ArchTask { ArchTask { context: Unique::dangling(), address_space: AddressSpace::current(), kernel_stack: alloc::vec![0u8; KERNEL_STACK_SIZE].into_boxed_slice(), user: false, fsbase: VirtAddr::null(), gsbase: VirtAddr::null(), fpu_storage: None, symtab: None, } } pub fn new_kernel(entry_point: VirtAddr, enable_interrupts: bool) -> ArchTask { let switch_stack = Self::alloc_switch_stack().unwrap(); let task_stack = unsafe { alloc_zeroed(Layout::from_size_align_unchecked( KERNEL_STACK_SIZE, PAGE_SIZE, )) .add(KERNEL_STACK_SIZE) }; let address_space = AddressSpace::current(); let mut stack_ptr = switch_stack.value(); let mut stack = Stack::new(&mut stack_ptr); let kframe = unsafe { stack.offset::() }; *kframe = InterruptErrorFrame::default(); kframe.frame.ss = (KERNEL_DS_IDX as usize) << 3; kframe.frame.cs = (KERNEL_CS_IDX as usize) << 3; kframe.frame.rip = entry_point.value(); kframe.frame.rsp = task_stack as usize; kframe.frame.rflags = if enable_interrupts { 0x200 } else { 0 }; let context = unsafe { stack.offset::() }; *context = Context::default(); context.rip = iretq_init as usize; Self { context: unsafe { Unique::new_unchecked(context) }, address_space, kernel_stack: alloc::vec![0u8; KERNEL_STACK_SIZE].into_boxed_slice(), user: false, fsbase: VirtAddr::null(), gsbase: unsafe { VirtAddr::new(rdmsr(IA32_GS_BASE) as usize) }, fpu_storage: None, symtab: None, } } pub fn exec( &mut self, vmem: &mut Vmem, file: FileRef, argv: &[&[u8]], envp: &[&[u8]], ) -> KResult<()> { interrupts::disable(); let userland_entry = elf::load_elf(file)?; self.kernel_stack = alloc::vec![0u8; KERNEL_STACK_SIZE].into_boxed_slice(); self.fsbase = userland_entry.fsbase.unwrap_or(VirtAddr::null()); self.gsbase = unsafe { VirtAddr::new_unchecked(rdmsr(IA32_GS_BASE) as usize) }; self.user = true; self.address_space = userland_entry.addr_space; *vmem = userland_entry.vmem; self.address_space.switch(); // userland_entry self.address_space.with_mapper(|mut mapper| { vmem.map_area( USER_STACK_BOTTOM, USER_STACK_TOP, MMapFlags::empty(), MMapProt::PROT_READ | MMapProt::PROT_WRITE | MMapProt::PROT_EXEC, MMapKind::Anonymous, &mut mapper, ) })?; let stack_addr = USER_STACK_TOP - core::mem::size_of::(); let mut stack_addr = stack_addr.value(); let mut stack = Stack::new(&mut stack_addr); fn push_strs(strs: &[&[u8]], stack: &mut Stack) -> Vec { let mut tops = Vec::new(); for slice in strs.iter() { unsafe { stack.push(0u8); stack.push_bytes(slice); } tops.push(stack.top()); } tops } let envp_tops = push_strs(envp, &mut stack); let argv_tops = push_strs(argv, &mut stack); stack.align_down(16); let size = envp.len() + 1 + argv.len() + 1 + 1; if size % 2 == 1 { unsafe { stack.push(0u64); } } unsafe { stack.push(0usize); stack.push(AuxvType::AtNull); stack.push(userland_entry.hdr); stack.push(0u64); for envp_top in envp_tops.iter() { stack.push(*envp_top); } stack.push(0u64); for argv_top in argv_tops.iter() { stack.push(*argv_top); } stack.push(argv_tops.len()); } core::mem::drop(argv_tops); core::mem::drop(envp_tops); assert_eq!(stack.top() % 16, 0); self.fpu_storage = Some(Self::alloc_fpu_storage()); self.context = Unique::dangling(); self.symtab = userland_entry.symtab; unsafe { exec_entry(userland_entry.entry_point.value(), stack.top(), 0x200); } } pub fn fork(&mut self) -> KResult { assert!(self.user, "Cannot fork a kernel task"); let address_space = self.address_space.fork(true)?; unsafe { tlb::flush_all() }; let switch_stack = Self::alloc_switch_stack()?.as_raw_ptr_mut::(); let mut old_rsp = self.kernel_stack.as_ptr() as usize + self.kernel_stack.len(); let mut old_stack = Stack::new(&mut old_rsp); let mut new_rsp = switch_stack as usize; let mut new_stack = Stack::new(&mut new_rsp); unsafe { let new_frame = new_stack.offset::(); let old_frame = old_stack.offset::(); *new_frame = *old_frame; new_frame.frame.rax = 0x0; // fork return value // fixme: having interrupts enabled between the context switch and fork_init being called will clobber the stack new_frame.frame.rflags = old_frame.frame.rflags & !0x200; } let context = unsafe { new_stack.offset::() }; *context = Context::default(); context.rip = fork_init as usize; let mut fpu_storage = Self::alloc_fpu_storage(); fpu_storage.copy_from_slice(self.fpu_storage.as_ref().unwrap().as_slice()); Ok(Self { context: unsafe { Unique::new_unchecked(context) }, address_space, user: true, kernel_stack: alloc::vec![0u8; KERNEL_STACK_SIZE].into_boxed_slice(), fsbase: self.fsbase, gsbase: self.gsbase, fpu_storage: Some(fpu_storage), symtab: self.symtab.clone(), }) } pub fn clone_process( &self, entry_point: VirtAddr, user_stack: VirtAddr, args: VirtAddr, r8: usize, r9: usize, syscall_frame: &InterruptFrame, ) -> KResult { assert!(self.user, "Cannot clone a kernel task"); let address_space = AddressSpace::current().fork(true)?; let switch_stack = Self::alloc_switch_stack()?.as_raw_ptr_mut::(); let mut new_rsp = switch_stack as usize; let mut new_stack = Stack::new(&mut new_rsp); let new_frame = unsafe { new_stack.offset::() }; *new_frame = InterruptErrorFrame::default(); new_frame.frame.cs = syscall_frame.cs; new_frame.frame.ss = syscall_frame.ss; new_frame.frame.r8 = r8; new_frame.frame.r9 = r9; new_frame.frame.rdi = args.value(); new_frame.frame.rip = entry_point.value(); new_frame.frame.rsp = user_stack.value(); new_frame.frame.rflags = 0x200; let context = unsafe { new_stack.offset::() }; *context = Context::default(); context.rip = fork_init as usize; let mut fpu_storage = Self::alloc_fpu_storage(); fpu_storage.copy_from_slice(self.fpu_storage.as_ref().unwrap().as_slice()); Ok(Self { context: unsafe { Unique::new_unchecked(context) }, address_space, user: true, fpu_storage: Some(fpu_storage), gsbase: self.gsbase, fsbase: self.fsbase, kernel_stack: alloc::vec![0u8; KERNEL_STACK_SIZE].into_boxed_slice(), symtab: self.symtab.clone(), }) } fn alloc_fpu_storage() -> Box<[u8]> { unsafe { let xsave_size = CpuId::new().get_extended_state_info().unwrap().xsave_size(); let layout = Layout::from_size_align(xsave_size as usize, 16).unwrap(); let ptr = alloc_zeroed(layout); Box::from_raw(core::ptr::slice_from_raw_parts_mut( ptr, xsave_size as usize, )) } } fn alloc_switch_stack() -> KResult { Ok(alloc_kernel_frames(1)?.start_address().as_hhdm_virt() + PAGE_SIZE) } pub fn set_fsbase(&mut self, addr: VirtAddr) { self.fsbase = addr; unsafe { wrmsr(IA32_FS_BASE, self.fsbase.value() as u64); } } pub fn setup_signal_stack( frame: &mut InterruptFrame, signal: Signal, handler: VirtAddr, _syscall_result: isize, ) -> KResult<()> { const TRAMPOLINE: &[u8] = &[ 0xb8, 0x0f, 0x00, 0x00, 0x00, // mov eax, 15 0x0f, 0x05, // syscall 0x90, // nop (for alignment) ]; if frame.cs & 0x3 == 0 { return Ok(()); } let mut rsp = frame.rsp; let mut stack = Stack::new(&mut rsp); // red zone stack.skip_by(128); unsafe { stack.push_bytes(TRAMPOLINE); stack.push(stack.top()); } frame.rip = handler.value(); frame.rsp = rsp; frame.rdi = signal as usize; Ok(()) } pub fn setup_sigreturn_stack( &self, current_frame: &mut InterruptFrame, signaled_frame: &InterruptFrame, ) { *current_frame = *signaled_frame; } } ================================================ FILE: src/arch/x86_64/time.rs ================================================ use core::sync::atomic::{AtomicUsize, Ordering}; use x86::io::{inb, outb}; use crate::{userland::syscall::syscall_impl::time::TimeSpec, util::IrqMutex}; const PIT_FREQUENCY_HZ: usize = 1000; pub const PIT_DIVIDEND: usize = 1193182; static UPTIME_RAW: AtomicUsize = AtomicUsize::new(0); static UPTIME_SEC: AtomicUsize = AtomicUsize::new(0); pub static EPOCH: AtomicUsize = AtomicUsize::new(usize::MAX); pub static RT_CLOCK: IrqMutex = IrqMutex::new(TimeSpec::zero()); pub fn get_uptime_ns() -> usize { let ts = get_rt_clock(); (ts.tv_sec * 1000000000 + ts.tv_nsec) as usize } pub fn get_uptime_ms() -> usize { get_uptime_ns() / 1000000 } pub fn get_rt_clock() -> TimeSpec { *RT_CLOCK.lock() } pub fn get_pit_count() -> u16 { unsafe { outb(0x43, 0); let lower = inb(0x40) as u16; let higher = inb(0x40) as u16; (higher << 8) | lower } } pub fn set_reload_value(new_count: u16) { unsafe { outb(0x43, 0x34); outb(0x40, new_count as u8); outb(0x40, (new_count >> 8) as u8); } } pub fn set_pit_frequency(frequency: usize) { let mut new_divisor = PIT_DIVIDEND / frequency; if PIT_DIVIDEND % frequency > frequency / 2 { new_divisor += 1; } set_reload_value(new_divisor as u16); } pub fn pit_irq() { { let interval = TimeSpec { tv_sec: 0, tv_nsec: (1000000000 / PIT_FREQUENCY_HZ) as isize, }; let mut clk = RT_CLOCK.lock(); if clk.tv_nsec + interval.tv_nsec > 999999999 { let diff = (clk.tv_nsec + interval.tv_nsec) - 1000000000; clk.tv_nsec = diff; clk.tv_sec += 1; } else { clk.tv_nsec += interval.tv_nsec; } } let value = UPTIME_RAW.fetch_add(1, Ordering::Relaxed); if value % PIT_FREQUENCY_HZ == 0 { UPTIME_SEC.fetch_add(1, Ordering::Relaxed); } } pub fn init(boot_time: i64) { EPOCH.store(boot_time as usize, Ordering::SeqCst); RT_CLOCK.lock().tv_sec = boot_time as isize; set_pit_frequency(PIT_FREQUENCY_HZ); } ================================================ FILE: src/backtrace.rs ================================================ use core::{alloc::Layout, mem::size_of, panic::PanicInfo}; use alloc::vec::Vec; use spin::Once; use x86_64::instructions::interrupts; use xmas_elf::{ ElfFile, sections::{SectionData, ShType}, symbol_table::Entry, }; use crate::{ fb_println, graphics::FRAMEBUFFER, kerror, mem::{addr::VirtAddr, addr_space::AddressSpace, consts::PAGE_SIZE}, task::get_scheduler, userland::elf::SymTabEntry, util::{KResult, SavedInterruptStatus}, }; pub static KERNEL_ELF: Once> = Once::new(); fn print_symbol(rip: usize, symtab: &Option>, depth: usize) { if let Some(symbol_table) = symtab { let mut name = None; for data in symbol_table { let st_value = data.value as usize; let st_size = data.size as usize; if rip >= st_value && rip < (st_value + st_size) { name = Some(data.name.clone()); } } if let Some(name) = name { serial0_println!("{:>2}: 0x{:016x} - {}", depth, rip, name); } else { serial0_println!( "{:>2}: 0x{:016x} - (symbol not found)", depth, rip ); } } else { serial0_println!("{:>2}: 0x{:016x} - (no symbol table)", depth, rip); } } pub fn unwind_user_stack_from(mut rbp: usize, mut rip: usize) { let _guard = SavedInterruptStatus::save(); interrupts::disable(); let mut addr_space = AddressSpace::current(); if rbp == 0 { serial0_println!(""); return; } let current = get_scheduler().current_task_opt(); let symtab = if let Some(current) = current { let s = current.arch_mut().symtab.clone(); if s.is_none() { serial0_println!( "Warning: Couldn't find symbol table for pid {}", current.pid().as_usize() ); } s } else { serial0_println!("Warning: Couldn't lock current scheduler task"); None }; serial0_println!("---BEGIN BACKTRACE---"); print_symbol(rip, &symtab, 0); for depth in 1..17 { if let Some(rip_rbp) = rbp.checked_add(size_of::()) { let rip_rbp = unsafe { VirtAddr::new_unchecked(rip_rbp) }; let translated = addr_space.with_mapper(|mapper| mapper.translate(rip_rbp)); if rip_rbp.value() < PAGE_SIZE || translated.is_none() { serial0_println!("{:>2}: ", depth); break; } rip = unsafe { rip_rbp.read::().unwrap_or(0) }; if rip == 0 || rbp == 0 { break; } unsafe { rbp = *(rbp as *const usize); } print_symbol(rip, &symtab, depth); } else { break; } } serial0_println!("---END BACKTRACE---"); } pub fn unwind_stack() -> KResult<()> { let _guard = SavedInterruptStatus::save(); interrupts::disable(); let mut addr_space = AddressSpace::current(); let kernel_elf = KERNEL_ELF .get() .ok_or(kerror!("KERNEL_ELF not initialized"))?; let mut symbol_table = None; for section in kernel_elf.section_iter() { if section.get_type() == Ok(ShType::SymTab) { let section_data = section .get_data(kernel_elf) .map_err(|_| kerror!("Failed to get kernel section data"))?; if let SectionData::SymbolTable64(symtab) = section_data { symbol_table = Some(symtab); } } } let symbol_table = symbol_table.ok_or(kerror!("No symbol table available"))?; let mut rbp: usize; unsafe { core::arch::asm!("mov {}, rbp", out(reg) rbp); } if rbp == 0 { serial0_println!(""); return Ok(()); } serial0_println!("---BEGIN BACKTRACE---"); for depth in 0..16 { if let Some(rip_rbp) = rbp.checked_add(size_of::()) { let rip_rbp = unsafe { VirtAddr::new_unchecked(rip_rbp) }; let translated = addr_space.with_mapper(|mapper| mapper.translate(rip_rbp)); if translated.is_none() { serial0_println!("{:>2}: ", depth); break; } let rip = unsafe { rip_rbp.read::().unwrap_or(0) }; if rip == 0 || rbp == 0 { break; } unsafe { rbp = *(rbp as *const usize); } let mut name = None; for data in symbol_table { let st_value = data.value() as usize; let st_size = data.size() as usize; if rip >= st_value && rip < (st_value + st_size) { let mangled_name = data.get_name(kernel_elf).unwrap_or(""); name = Some(rustc_demangle::demangle(mangled_name)); } } if let Some(name) = name { serial0_println!("{:>2}: 0x{:016x} - {}", depth, rip, name); } else { serial0_println!("{:>2}: 0x{:016x} - ", depth, rip); } } else { break; } } serial0_println!("---END BACKTRACE---"); Ok(()) } #[panic_handler] fn rust_panic(info: &PanicInfo) -> ! { interrupts::disable(); let panic_msg = info.message(); serial0_println!("Panicked at '{}'", panic_msg); if FRAMEBUFFER.get().is_some() { fb_println!("Panicked at '{}'", panic_msg); } if let Some(panic_location) = info.location() { serial0_println!("{}", panic_location); if FRAMEBUFFER.get().is_some() { fb_println!("{}", panic_location); } } match unwind_stack() { Ok(()) => {} Err(e) => crate::serial::_print0(format_args!("Error unwinding stack: {:?}\n", e.msg())), } crate::hcf(); } #[allow(non_snake_case)] #[unsafe(no_mangle)] extern "C" fn _Unwind_Resume(unwind_context_ptr: usize) -> ! { serial0_println!("{:#x}", unwind_context_ptr); crate::hcf(); } #[lang = "eh_personality"] #[unsafe(no_mangle)] extern "C" fn rust_eh_personality() -> ! { serial0_println!("Poisoned function `rust_eh_personality` was called."); crate::hcf() } #[alloc_error_handler] fn handle_alloc_error(layout: Layout) -> ! { panic!("Alloc Error for layout {:?}", layout) } ================================================ FILE: src/fs/devfs/fb.rs ================================================ use alloc::sync::Arc; use spin::Once; use crate::{ fs::{initramfs::get_root, opened_file::OpenFlags, File, FsNode, INode}, graphics::fb, kerror, mem::addr::VirtAddr, userland::buffer::{UserBuffer, UserBufferMut, UserBufferReader, UserBufferWriter}, util::KResult, }; pub static DEV_FB0: Once> = Once::new(); pub fn init() { let fb0 = Arc::new(FbDevice); get_root() .unwrap() .root_dir() .lookup("dev") .unwrap() .as_dir() .unwrap() .insert(INode::File(fb0.clone())); DEV_FB0.call_once(|| fb0); } pub struct FbDevice; impl FsNode for FbDevice { fn get_name(&self) -> alloc::string::String { "fb0".into() } } impl File for FbDevice { fn read(&self, offset: usize, buf: UserBufferMut, _options: &OpenFlags) -> KResult { assert_eq!(offset % 4, 0); let buf_len = buf.len(); assert_eq!(buf_len % 4, 0); let mut writer = UserBufferWriter::from_buf(buf); let mut fb = fb(); let mem = fb.frame_mut(); let start = offset / 4; let len = buf_len / 4; let end = (start + len).min(mem.len()); let mem = mem[start..end].iter().flat_map(|pixel| pixel.to_le_bytes()); for byte in mem { writer.write(byte)?; } Ok((end - start) * 4) } fn write(&self, offset: usize, buf: UserBuffer<'_>, _options: &OpenFlags) -> KResult { assert_eq!(offset % 4, 0); let buf_len = buf.len(); assert_eq!(buf_len % 4, 0); let mut reader = UserBufferReader::from_buf(buf); let mut fb = fb(); let mem = fb.frame_mut(); let mut i = 0; while (offset / 4 + i) < mem.len() && i < buf_len / 4 { let pixel = reader.read::()?; // let pixel = u32::from_le_bytes([byte0, byte1, byte2, byte3]); mem[offset / 4 + i] = pixel; i += 1; } fb.present(); Ok(i * 4) } fn ioctl(&self, cmd: usize, arg: usize) -> KResult { const FBIOGET_VSCREENINFO: usize = 0x4600; // const FBIOGET_FSCREENINFO: usize = 0x4602; match cmd { FBIOGET_VSCREENINFO => { let fb = fb(); let info = FbVarScreenInfo { xres: fb.width() as u32, yres: fb.height() as u32, xres_virtual: fb.width() as u32, yres_virtual: fb.height() as u32, bpp: fb.bpp() as u32, ..FbVarScreenInfo::default() }; unsafe { VirtAddr::new(arg).write_user(info) }?; } // FBIOGET_FSCREENINFO => { // } _ => return Err(kerror!(EINVAL, "ioctl(): unknown cmd")), } Ok(0) } } #[repr(C)] #[derive(Clone, Copy, Default)] struct FbBitField { offset: u32, length: u32, msb_right: u32, } #[repr(C)] #[derive(Clone, Copy, Default)] struct FbVarScreenInfo { xres: u32, yres: u32, xres_virtual: u32, yres_virtual: u32, xoffset: u32, yoffset: u32, bpp: u32, grayscale: u32, red: FbBitField, green: FbBitField, blue: FbBitField, transp: FbBitField, nonstd: u32, activate: u32, height_mm: u32, width_mm: u32, accel_flags: u32, pixclock: u32, left_margin: u32, right_margin: u32, upper_margin: u32, lower_margin: u32, hsync_len: u32, vsync_len: u32, sync: u32, vmode: u32, rotate: u32, colorspace: u32, reserved: [u32; 4], } ================================================ FILE: src/fs/devfs/input.rs ================================================ //! TODO: make this more like an actual linux keyboard device file use alloc::sync::Arc; use pc_keyboard::{KeyEvent, KeyState}; use spin::{mutex::SpinMutex, Once}; use crate::{ fs::{initramfs::get_root, opened_file::OpenFlags, File, FsNode, INode}, userland::buffer::{UserBufferMut, UserBufferWriter}, util::KResult, }; pub static KBD_DEVICE: Once> = Once::new(); pub fn init() { let kbd = Arc::new(KbdDevice::new()); get_root() .unwrap() .root_dir() .lookup("dev") .unwrap() .as_dir() .unwrap() .insert(INode::File(kbd.clone())); KBD_DEVICE.call_once(|| kbd); } pub struct KbdDevice { keys_pressed: Arc>, } impl KbdDevice { pub fn new() -> Self { Self { keys_pressed: Arc::new(SpinMutex::new([0u8; 256])), } } pub fn handle_kbd_irq(&self, key_evt: &KeyEvent) { match key_evt.state { KeyState::Down => self.keys_pressed.lock()[key_evt.code as u8 as usize] = 1, KeyState::Up => self.keys_pressed.lock()[key_evt.code as u8 as usize] = 0, _ => {} } } } impl Default for KbdDevice { fn default() -> Self { Self::new() } } impl FsNode for KbdDevice { fn get_name(&self) -> alloc::string::String { "kbd".into() } } impl File for KbdDevice { fn read(&self, _offset: usize, buf: UserBufferMut, _options: &OpenFlags) -> KResult { let mut writer = UserBufferWriter::from_buf(buf); let keys = *self.keys_pressed.lock(); writer.write(keys) } } ================================================ FILE: src/fs/devfs/mod.rs ================================================ pub mod fb; pub mod input; pub mod null; pub mod socket; pub mod tty; pub mod urandom; pub fn init() { self::tty::init(); self::null::init(); self::urandom::init(); self::fb::init(); self::input::init(); self::socket::init(); } ================================================ FILE: src/fs/devfs/null.rs ================================================ use alloc::{borrow::ToOwned, sync::Arc}; use spin::Once; use crate::{ fs::{ initramfs::get_root, opened_file::OpenFlags, File, FileMode, FsNode, INode, Stat, S_IFCHR, }, userland::buffer::UserBufferWriter, }; static DEV_NULL: Once> = Once::new(); pub fn init() { let null = Arc::new(NullDevice); get_root() .unwrap() .root_dir() .lookup("dev") .unwrap() .as_dir() .unwrap() .insert(INode::File(null.clone())); DEV_NULL.call_once(|| null); } pub struct NullDevice; impl FsNode for NullDevice { fn get_name(&self) -> alloc::string::String { "null".to_owned() } } impl File for NullDevice { fn write( &self, _offset: usize, buf: crate::userland::buffer::UserBuffer<'_>, _options: &OpenFlags, ) -> crate::util::KResult { Ok(buf.len()) } fn read( &self, _offset: usize, buf: crate::userland::buffer::UserBufferMut, _options: &OpenFlags, ) -> crate::util::KResult { let mut writer = UserBufferWriter::from_buf(buf); writer.write_bytes(&[0x05])?; // EOF Ok(writer.written_len()) } fn stat(&self) -> crate::util::KResult { Ok(Stat { inode_no: 4, mode: FileMode(S_IFCHR), ..Stat::zeroed() }) } } ================================================ FILE: src/fs/devfs/socket.rs ================================================ use core::sync::atomic::{AtomicUsize, Ordering}; use crate::{ fs::{File, FsNode}, kerror, mem::addr::VirtAddr, util::{KError, KResult}, }; pub fn init() {} #[repr(C)] #[non_exhaustive] pub enum Domain { Unix = 0, Inet = 2, } impl TryFrom for Domain { type Error = KError<'static>; fn try_from(value: usize) -> Result { match value { 0 => Ok(Self::Unix), 2 => Ok(Self::Inet), _ => Err(kerror!(EINVAL, "invalid socket domain")), } } } #[repr(C)] #[non_exhaustive] pub enum SocketType { Stream = 1, Datagram = 2, Raw = 3, SeqPacket = 5, Packet = 10, } impl TryFrom for SocketType { type Error = KError<'static>; fn try_from(value: usize) -> Result { match value { 1 => Ok(Self::Stream), 2 => Ok(Self::Datagram), 3 => Ok(Self::Raw), 5 => Ok(Self::SeqPacket), 10 => Ok(Self::Packet), _ => Err(kerror!(EINVAL, "invalid socket type")), } } } #[repr(C)] #[non_exhaustive] pub enum Protocol { Ipv4 = 4, Tcp = 6, Udp = 17, } impl TryFrom for Protocol { type Error = KError<'static>; fn try_from(value: usize) -> Result { match value { 4 => Ok(Self::Ipv4), 6 => Ok(Self::Tcp), 17 => Ok(Self::Udp), _ => Err(kerror!(EINVAL, "invalid socket protocol")), } } } pub struct Socket { pub id: usize, // pub handle: SocketHandle, pub domain: Domain, pub typ: SocketType, pub protocol: Protocol, } static NEXT_ID: AtomicUsize = AtomicUsize::new(0); impl Socket { pub fn alloc_id() -> usize { NEXT_ID.fetch_add(1, Ordering::SeqCst) } // pub fn new() -> Self { // Self { // id: Self::alloc_id(), // handle: (), // domain: (), // typ: (), // protocol: (), // } // } } impl FsNode for Socket { fn get_name(&self) -> alloc::string::String { alloc::format!("socket{}", self.id) } } #[derive(Clone, Copy)] #[repr(C, packed)] pub struct SockAddrInet { family: u16, port: [u8; 2], addr: [u8; 4], zero: [u8; 8], } pub fn read_sockaddr(addr: VirtAddr, len: usize) -> KResult { let family = unsafe { addr.read_volatile::()? }; let sockaddr = match Domain::try_from(family as usize)? { Domain::Inet => { if len < core::mem::size_of::() { return Err(kerror!(EINVAL, "read_sockaddr(): buffer overflow")); } unsafe { addr.read_volatile::()? } } Domain::Unix => { todo!() } }; Ok(sockaddr) } pub fn write_sockaddr( sockaddr: SockAddrInet, dst: Option, socklen: Option, ) -> KResult<()> { if let Some(dst) = dst { unsafe { dst.write_volatile(sockaddr) }?; } if let Some(socklen) = socklen { unsafe { socklen.write_volatile(core::mem::size_of::() as u32) }?; } Ok(()) } impl File for Socket { fn ioctl(&self, cmd: usize, _arg: usize) -> KResult { const FIONBIO: usize = 0x5421; match cmd { FIONBIO => { // todo: set/clear non block flag } _ => return Err(kerror!(EINVAL, "ioctl(): unknown cmd for socket")), } Ok(0) } } ================================================ FILE: src/fs/devfs/tty.rs ================================================ use core::fmt::Debug; use alloc::{ borrow::ToOwned, string::String, sync::{Arc, Weak}, vec::Vec, }; use bitflags::bitflags; use spin::Once; use crate::{ fb_print, graphics::{self, render_text_buf}, kerror, mem::addr::VirtAddr, task::{current_task, get_scheduler, group::TaskGroup, signal::SIGINT, wait_queue::WaitQueue}, userland::buffer::{UserBuffer, UserBufferMut, UserBufferReader, UserBufferWriter}, util::{ ctypes::c_int, errno::Errno, error::KResult, lock::IrqMutex, ringbuffer::RingBuffer, KError, }, vga_text, }; use crate::fs::{ initramfs::{dir::InitRamFsDir, get_root}, opened_file::OpenFlags, path::Path, File, FileMode, FileRef, FsNode, INode, PollStatus, Stat, POLL_WAIT_QUEUE, S_IFCHR, }; pub static TTY: Once> = Once::new(); pub fn init() { let tty = Arc::new(Tty::new("tty")); TTY.call_once(|| tty.clone()); get_root() .unwrap() .lookup(Path::new("dev"), true) .unwrap() .as_dir() .unwrap() .insert(INode::File(tty)); } bitflags! { #[repr(C)] #[derive(Clone, Copy, Debug)] pub struct LFlag: u32 { const ICANON = 0o0000002; const ECHO = 0o0000010; } } impl Default for LFlag { fn default() -> Self { Self::all() } } bitflags! { #[repr(C)] #[derive(Clone, Copy, Debug)] pub struct IFlag: u32 { const IGNBRK = 0o0000001; const BRKINT = 0o0000002; const IGNPAR = 0o0000004; const PARMRK = 0o0000010; const INPCK = 0o0000020; const ISTRIP = 0o0000040; const INLCR = 0o0000100; const IGNCR = 0o0000200; const ICRNL = 0o0000400; const IUCLC = 0o0001000; const IXON = 0o0002000; const IXANY = 0o0004000; const IXOFF = 0o0010000; const IMAXBEL = 0o0020000; const IUTF8 = 0o0040000; } } impl Default for IFlag { fn default() -> Self { Self::ICRNL } } #[repr(C)] #[derive(Clone, Copy)] pub struct Termios { iflag: IFlag, oflag: u32, cflag: u32, lflag: LFlag, cc: [u8; 0], reserved: [u32; 3], ispeed: u32, ospeed: u32, } impl Termios { pub fn is_cooked(&self) -> bool { self.lflag.contains(LFlag::ICANON) } } impl Default for Termios { fn default() -> Self { Termios { iflag: IFlag::ICRNL, lflag: LFlag::ICANON | LFlag::ECHO, oflag: 0, cflag: 0, cc: [0; 0], reserved: [0; 3], ispeed: 0, ospeed: 0, } } } impl Debug for Termios { fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { f.debug_struct("Termios") .field("iflag", &self.iflag) .field("lflag", &self.lflag) .finish() } } pub enum LineControl { Backspace, Echo(u8), } pub struct LineDiscipline { wait_queue: WaitQueue, current_line: IrqMutex>, buf: IrqMutex>, termios: IrqMutex, foreground_group: IrqMutex>>, } impl LineDiscipline { pub fn new() -> Self { Self { wait_queue: WaitQueue::new(), current_line: IrqMutex::new(Vec::new()), buf: IrqMutex::new(RingBuffer::new()), termios: IrqMutex::new(Termios::default()), foreground_group: IrqMutex::new(Weak::new()), } } pub fn is_readable(&self) -> bool { self.buf.lock().is_readable() } pub fn is_writable(&self) -> bool { self.buf.lock().is_writable() } pub fn foreground_group(&self) -> Option>> { self.foreground_group.lock().upgrade() } pub fn set_foreground_group(&self, pg: Weak>) { *self.foreground_group.lock() = pg; } fn _is_current_foreground(&self) -> bool { let pg = &*self.foreground_group.lock(); current_task().belongs_to_group(pg) || pg.upgrade().is_none() } pub fn write(&self, buf: UserBuffer<'_>, callback: F) -> KResult where F: Fn(LineControl), { let termios = self.termios.lock(); let mut current_line = self.current_line.lock(); let mut ringbuf = self.buf.lock(); let mut written_len = 0; let mut reader = UserBufferReader::from_buf(buf); while reader.remaining_len() > 0 { let mut tmp = [0; 1]; let copied_len = reader.read_bytes(&mut tmp)?; for ch in &tmp.as_slice()[..copied_len] { match ch { 0x03 if termios.is_cooked() => { if let Some(pg) = self.foreground_group() { pg.lock().signal(SIGINT); } } 0x08 if termios.is_cooked() => { if !current_line.is_empty() { current_line.pop(); callback(LineControl::Backspace); } } b'\r' if termios.iflag.contains(IFlag::ICRNL) => { current_line.push(b'\n'); ringbuf.push_slice(¤t_line); current_line.clear(); if termios.lflag.contains(LFlag::ECHO) { callback(LineControl::Echo(b'\r')); callback(LineControl::Echo(b'\n')); } } b'\n' => { current_line.push(b'\n'); ringbuf.push_slice(¤t_line); current_line.clear(); if termios.lflag.contains(LFlag::ECHO) { callback(LineControl::Echo(b'\n')); } } ch if termios.is_cooked() => { if 0x20 <= *ch && *ch < 0x7f { current_line.push(*ch); if termios.lflag.contains(LFlag::ECHO) { callback(LineControl::Echo(*ch)); } } } _ => { ringbuf.push(*ch).ok(); } } written_len += 1; } } get_scheduler().wake_all(&self.wait_queue); Ok(written_len) } pub fn read(&self, dst: UserBufferMut<'_>, options: &OpenFlags) -> KResult { let mut writer = UserBufferWriter::from_buf(dst); let timeout = if options.contains(OpenFlags::O_NONBLOCK) { Some(0) } else { None }; self.wait_queue.sleep_signalable_until(timeout, || { // todo: figure out how to get this working // if !self.is_current_foreground() { // return Ok(None) // } let mut buf_lock = self.buf.lock(); while writer.remaining_len() > 0 { if let Some(slice) = buf_lock.pop_slice(writer.remaining_len()) { writer.write_bytes(slice)?; } else { break; } } if writer.written_len() > 0 { Ok(Some(writer.written_len())) } else { Ok(None) } }) } } impl Default for LineDiscipline { fn default() -> Self { Self::new() } } pub struct Tty { name: String, discipline: LineDiscipline, } impl Tty { pub fn new(name: &str) -> Self { Self { name: name.to_owned(), discipline: LineDiscipline::new(), } } pub fn set_cooked_mode(&self, cooked: bool) { if cooked { self.discipline.termios.lock().lflag |= LFlag::ICANON | LFlag::ECHO; } else { self.discipline.termios.lock().lflag &= !(LFlag::ICANON | LFlag::ECHO); } } pub fn input_char(&self, ch: u8) { self.discipline .write(UserBuffer::from_slice(&[ch]), |ctrl| match ctrl { LineControl::Backspace => { // serial1_print!("\x08 \x08"); graphics::backspace(); } LineControl::Echo(ch) => { self.write(0, UserBuffer::from_slice(&[ch]), &OpenFlags::empty()) .ok(); } }) .ok(); } pub fn set_foreground_group(&self, pg: Weak>) { self.discipline.set_foreground_group(pg) } } impl FsNode for Tty { fn get_name(&self) -> String { self.name.clone() } } const TCGETS: usize = 0x5401; const TCSETS: usize = 0x5402; const TCSETSW: usize = 0x5403; const TIOCGPGRP: usize = 0x540f; const TIOCSPGRP: usize = 0x5410; const TIOCGWINSZ: usize = 0x5413; #[repr(C)] #[derive(Copy, Clone)] struct WinSize { ws_row: u16, ws_col: u16, ws_xpixel: u16, ws_ypixel: u16, } impl File for Tty { fn ioctl(&self, cmd: usize, arg: usize) -> KResult { match cmd { TCGETS => { let termios = *self.discipline.termios.lock(); let arg = VirtAddr::new(arg); unsafe { arg.write_user(termios) }?; } TCSETS | TCSETSW => { let arg = VirtAddr::new(arg); let termios = unsafe { arg.read_user::()? }; *self.discipline.termios.lock() = termios; } TIOCGPGRP => { let group = self .discipline .foreground_group() .unwrap_or(current_task().group.borrow().upgrade().unwrap()); let id = group.lock().pgid(); let arg = VirtAddr::new(arg); unsafe { arg.write_user(id) }?; } TIOCSPGRP => { let arg = VirtAddr::new(arg); let pgid = unsafe { arg.read_user::()? }; let pg = get_scheduler().find_or_create_group(pgid); self.discipline.set_foreground_group(Arc::downgrade(&pg)); } TIOCGWINSZ => { let winsize = WinSize { ws_row: vga_text::BUFFER_HEIGHT as u16, ws_col: vga_text::BUFFER_WIDTH as u16, ws_xpixel: 0, ws_ypixel: 0, }; let arg = VirtAddr::new(arg); unsafe { arg.write_user(winsize) }?; } _ => return Err(kerror!(ENOTTY, "ioctl(): command not found")), } Ok(0) } fn stat(&self) -> KResult { Ok(Stat { inode_no: 3, mode: FileMode::new(S_IFCHR | 0o666), ..Stat::zeroed() }) } fn read(&self, _offset: usize, buf: UserBufferMut, options: &OpenFlags) -> KResult { let read_len = self.discipline.read(buf, options); match read_len { Ok(read_len) => { if read_len > 0 { get_scheduler().wake_all(&POLL_WAIT_QUEUE); } Ok(read_len) } Err(KError { errno, .. }) if options.contains(OpenFlags::O_NONBLOCK) && errno == Some(Errno::EINTR) => { Ok(0) } Err(e) => Err(e), } } fn write(&self, _offset: usize, buf: UserBuffer<'_>, _options: &OpenFlags) -> KResult { // let mut tmp = [0; 1]; // let mut total_len = 0; let reader = UserBufferReader::from_buf(buf); let total_len = parse(reader)?; if total_len > 0 { render_text_buf(); get_scheduler().wake_all(&POLL_WAIT_QUEUE); } Ok(total_len) } fn poll(&self) -> KResult { let mut status = PollStatus::empty(); // if self.discipline.is_readable() { status |= PollStatus::POLLIN; // } // if self.discipline.is_writable() { status |= PollStatus::POLLOUT; // } Ok(status) } } fn parse(mut reader: UserBufferReader) -> KResult { let mut bytes = alloc::vec![0u8; reader.remaining_len()]; reader.read_bytes(&mut bytes)?; let mut escape_codes = bytes.split(|b| *b == 0x1b); if bytes[0] != 0x1b { // print until the first escape code if let Some(next) = escape_codes.next() { fb_print!("{}", core::str::from_utf8(next).unwrap()); } else { return Ok(0); } } for chunk in escape_codes { if chunk.is_empty() { continue; } if chunk[0] != b'[' { continue; } let chunk = &chunk[1..]; // iterate through the chunk until we find one of the ANSI "functions" // const ANSI_FUNCTIONS: &[u8] = b"ABCDEFGHJKSTsufm"; const ANSI_FUNCTIONS: &[u8] = b"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"; let res = chunk .iter() .enumerate() .find(|(_i, byte)| ANSI_FUNCTIONS.contains(*byte)); let (f_idx, function) = if let Some(res) = res { res } else { unreachable!() }; // get its arguments, if any let arguments = chunk[..f_idx] .split(|byte| *byte == b';') .collect::>(); let parse_usize = |arg: &[u8]| core::str::from_utf8(arg).unwrap().parse::(); let (x, y) = graphics::cursor_xy(); match *function { b'A' => { let n = parse_usize(arguments[0]).unwrap_or(1); graphics::set_cursor_y(y.saturating_sub(n)); } b'B' => { let n = parse_usize(arguments[0]).unwrap_or(1); graphics::set_cursor_y(y.saturating_add(n)); } b'C' => { let n = parse_usize(arguments[0]).unwrap_or(1); graphics::set_cursor_x(x.saturating_add(n)); } b'D' => { let n = parse_usize(arguments[0]).unwrap_or(1); graphics::set_cursor_x(x.saturating_sub(n)); } b'E' => { let n = parse_usize(arguments[0]).unwrap_or(1); graphics::set_cursor_x(0); graphics::set_cursor_y(y.saturating_add(n)); } b'F' => { let n = parse_usize(arguments[0]).unwrap_or(1); graphics::set_cursor_x(0); graphics::set_cursor_y(y.saturating_sub(n)); } b'G' | b'f' => { let n = parse_usize(arguments[0]).unwrap(); graphics::set_cursor_x(n); } b'H' => { if arguments[0].is_empty() { graphics::set_cursor_xy((0, 0)); } else { let n = parse_usize(arguments[0]).unwrap_or(0); let m = parse_usize(arguments[1]).unwrap_or(0); graphics::set_cursor_xy((n, m)); } } b'J' => { if arguments.is_empty() { graphics::clear_until_end(); } else { let n = parse_usize(arguments[0]).unwrap_or(0); match n { 0 => graphics::clear_until_end(), 1 => graphics::clear_until_beginning(), 2 => graphics::clear_screen(), 3 => todo!("erase saved lines"), _ => unimplemented!(), } } } b'K' => { if arguments.is_empty() { graphics::clear_until_eol(); } else { let n = parse_usize(arguments[0]).unwrap_or(0); match n { 0 => graphics::clear_until_eol(), 1 => graphics::clear_from_bol(), 2 => graphics::clear_line(), _ => unimplemented!(), } } } b'S' => { todo!("scroll up by N lines") } b'T' => { todo!("scroll down by N ines") } b's' => { todo!("save cursor position") } b'u' => { todo!("restore cursor postion") } b'm' => { // let arg0 = parse_usize(arguments[0]).unwrap() as u8; // match arg0 { // 0 => graphics::set_color_code(ColorCode::new(Color::White, Color::Black)), // 1 => {} // bold // 3 => {} // italic // 4 => {} // underline // 30..=37 => { // let color = graphics::get_color_code(); // graphics::set_color_code(ColorCode::new( // unsafe { core::mem::transmute(arg0 - 30) }, // color.background(), // )); // } // 40..=47 => { // let color = graphics::get_color_code(); // graphics::set_color_code(ColorCode::new(color.foreground(), unsafe { // core::mem::transmute(arg0 - 40) // })); // } // 90..=97 => { // todo!("bright foreground color") // } // 100..=107 => { // todo!("bright background color") // } // _ => todo!( // "Unknown ANSI function: {}", // core::str::from_utf8(chunk).unwrap() // ), // } } _function if chunk[0] == b'?' => { let n = parse_usize(&arguments[0][1..]).unwrap(); match n { /* Save cursor as in DECSC, xterm. After saving the cursor, switch to the Alternate Screen Buffer, clearing it first. */ 1049 => {} n => unimplemented!("Unknown ANSI extension function: {}", n), } } _ => { unimplemented!( "Unknown ANSI function: {}", core::str::from_utf8(chunk).unwrap() ) } } fb_print!("{}", core::str::from_utf8(&chunk[f_idx + 1..]).unwrap()); } Ok(reader.read_len()) } pub struct PtyMaster { wait_queue: WaitQueue, buf: IrqMutex>, discipline: LineDiscipline, } impl PtyMaster { pub fn new() -> KResult<(Arc, Arc)> { let master = Arc::new(PtyMaster { wait_queue: WaitQueue::new(), buf: IrqMutex::new(Vec::new()), discipline: LineDiscipline::new(), }); let slave = Arc::new(PtySlave::new(master.clone())); Ok((master, slave)) } } impl FsNode for PtyMaster { fn get_name(&self) -> String { "tty0".to_owned() } } impl File for PtyMaster { fn read(&self, _offset: usize, buf: UserBufferMut<'_>, options: &OpenFlags) -> KResult { let mut writer = UserBufferWriter::from_buf(buf); let timeout = if options.contains(OpenFlags::O_NONBLOCK) { Some(0) } else { None }; let read_len = self.wait_queue.sleep_signalable_until(timeout, || { let mut buf_lock = self.buf.lock(); if buf_lock.is_empty() { return Ok(None); } let copy_len = core::cmp::min(buf_lock.len(), writer.remaining_len()); writer.write_bytes(&buf_lock[..copy_len])?; buf_lock.drain(..copy_len); Ok(Some(copy_len)) })?; if read_len > 0 { get_scheduler().wake_all(&POLL_WAIT_QUEUE); } Ok(read_len) } fn write(&self, _offset: usize, buf: UserBuffer<'_>, _options: &OpenFlags) -> KResult { let written_len = self.discipline.write(buf, |ctrl| { let mut master_buf = self.buf.lock(); match ctrl { LineControl::Backspace => { master_buf.extend_from_slice(b"\x08 \x08"); } LineControl::Echo(ch) => { master_buf.push(ch); } } })?; if written_len > 0 { get_scheduler().wake_all(&POLL_WAIT_QUEUE); } Ok(written_len) } fn ioctl(&self, cmd: usize, _arg: usize) -> KResult { log::warn!("ioctl(): unknown cmd for PtyMaster ({:#x})", cmd); Ok(0) } fn stat(&self) -> KResult { Ok(Stat { inode_no: 5, mode: FileMode::new(S_IFCHR | 0o666), ..Stat::zeroed() }) } fn poll(&self) -> KResult { let mut status = PollStatus::empty(); if !self.buf.lock().is_empty() { status |= PollStatus::POLLIN; } if self.discipline.is_writable() { status |= PollStatus::POLLOUT; } Ok(status) } } pub struct PtySlave { master: Arc, } impl PtySlave { pub fn new(master: Arc) -> Self { Self { master } } } impl FsNode for PtySlave { fn get_name(&self) -> String { "ttyS0".to_owned() } } impl File for PtySlave { fn read(&self, _offset: usize, buf: UserBufferMut, options: &OpenFlags) -> KResult { let read_len = self.master.discipline.read(buf, options)?; if read_len > 0 { get_scheduler().wake_all(&POLL_WAIT_QUEUE); } Ok(read_len) } fn write(&self, _offset: usize, buf: UserBuffer<'_>, _options: &OpenFlags) -> KResult { let mut written_len = 0; let mut master_buf = self.master.buf.lock(); let mut reader = UserBufferReader::from_buf(buf); while reader.remaining_len() > 0 { let mut tmp = [0; 1]; let copied_len = reader.read_bytes(&mut tmp)?; for ch in &tmp[..copied_len] { match *ch { b'\n' => { master_buf.push(b'\r'); master_buf.push(b'\n'); } _ => { master_buf.push(*ch); } } } written_len += copied_len; } if written_len > 0 { get_scheduler().wake_all(&POLL_WAIT_QUEUE); } Ok(written_len) } fn stat(&self) -> KResult { Ok(Stat { inode_no: 6, mode: FileMode::new(S_IFCHR | 0o666), ..Stat::zeroed() }) } fn ioctl(&self, cmd: usize, _arg: usize) -> KResult { const TIOCSPTLCK: usize = 0x40045431; match cmd { TIOCSPTLCK => Ok(0), _ => { log::warn!("ioctl(): unknown cmd for PtySlave ({:#x})", cmd); Ok(0) } } } fn poll(&self) -> KResult { let mut status = PollStatus::empty(); if self.master.discipline.is_readable() { status |= PollStatus::POLLIN; } status |= PollStatus::POLLOUT; Ok(status) } } pub struct Ptmx { pts_dir: Arc, } impl Ptmx { pub fn new(pts_dir: Arc) -> Self { Self { pts_dir } } } impl FsNode for Ptmx { fn get_name(&self) -> String { todo!() } } impl File for Ptmx { fn open(&self, _options: &OpenFlags) -> KResult> { let (master, slave) = PtyMaster::new()?; self.pts_dir.add_file(slave); Ok(Some(master as FileRef)) } fn stat(&self) -> KResult { Ok(Stat { inode_no: 4, mode: FileMode::new(S_IFCHR | 0o666), ..Stat::zeroed() }) } fn read(&self, _offset: usize, _buf: UserBufferMut, _options: &OpenFlags) -> KResult { unreachable!() } fn write(&self, _offset: usize, _buf: UserBuffer<'_>, _options: &OpenFlags) -> KResult { unreachable!() } fn poll(&self) -> KResult { Ok(PollStatus::empty()) } } ================================================ FILE: src/fs/devfs/urandom.rs ================================================ use alloc::sync::Arc; use x86::random::rdrand_slice; use crate::{ fs::{initramfs::get_root, path::Path, File, FsNode, INode}, userland::buffer::UserBufferWriter, }; pub fn init() { get_root() .unwrap() .lookup(Path::new("dev"), true) .unwrap() .as_dir() .unwrap() .insert(INode::File(Arc::new(URandom))); } pub struct URandom; impl FsNode for URandom { fn get_name(&self) -> alloc::string::String { "urandom".into() } } impl File for URandom { fn read( &self, _offset: usize, buf: crate::userland::buffer::UserBufferMut, _options: &crate::fs::opened_file::OpenFlags, ) -> crate::util::KResult { let mut bytes = alloc::vec![0u8; buf.len()]; unsafe { rdrand_slice(&mut bytes); } let mut writer = UserBufferWriter::from_buf(buf); writer.write_bytes(&bytes)?; Ok(writer.written_len()) } } ================================================ FILE: src/fs/initramfs/dir.rs ================================================ use alloc::{ string::String, sync::{Arc, Weak}, vec::Vec, }; use crate::{ fs::{ alloc_inode_no, DirEntry, Directory, FileMode, FileRef, FileType, FsNode, INode, Stat, S_IFDIR, }, kerror, util::{lock::IrqMutex, KResult}, }; pub struct DirInner { pub children: Vec, pub stat: Stat, pub name: String, } pub struct InitRamFsDir { pub(super) parent: Weak, pub(super) inner: IrqMutex, } impl InitRamFsDir { pub fn new(name: String, inode_no: usize) -> InitRamFsDir { InitRamFsDir { parent: Weak::new(), inner: IrqMutex::new(DirInner { name, children: Vec::new(), stat: Stat { inode_no, mode: FileMode::new(S_IFDIR | 0o755), ..Stat::zeroed() }, }), } } pub fn add_dir(&self, name: String) -> Arc { let dir = Arc::new(InitRamFsDir::new(name, alloc_inode_no())); self.inner.lock().children.push(INode::Dir(dir.clone())); dir } pub fn add_file(&self, file: FileRef) { self.inner.lock().children.push(INode::File(file.clone())); } pub fn parent_dir(&self) -> Option> { self.parent.upgrade() } } impl Directory for InitRamFsDir { fn insert(&self, inode: INode) { self.inner.lock().children.push(inode); } fn lookup(&self, name: &str) -> KResult { let inode = self .inner .lock() .children .iter() .find(|child| child.get_name() == *name) .cloned() .ok_or(kerror!(ENOENT, "lookup(): not found"))?; Ok(inode) } fn stat(&self) -> KResult { Ok(self.inner.lock().stat) } fn readdir(&self, index: usize) -> KResult> { let entry = self .inner .lock() .children .get(index) .map(|entry| match entry { INode::Pipe(_) => unreachable!("Pipes should be in PipeFs"), INode::Dir(dir) => DirEntry { inode_no: dir.stat().unwrap().inode_no, file_type: FileType::Directory, name: dir.get_name(), }, INode::File(file) => DirEntry { inode_no: file.stat().unwrap().inode_no, file_type: FileType::Directory, name: file.get_name(), }, INode::Symlink(link) => DirEntry { inode_no: link.stat().unwrap().inode_no, file_type: FileType::Link, name: link.get_name(), }, }); Ok(entry) } fn unlink(&self, name: &str) -> KResult<()> { self.inner .lock() .children .retain(|child| child.get_name() != name); Ok(()) } } impl FsNode for InitRamFsDir { fn get_name(&self) -> String { self.inner.lock().name.clone() } } ================================================ FILE: src/fs/initramfs/file.rs ================================================ use alloc::{string::String, vec, vec::Vec}; use crate::{ fs::{opened_file::OpenFlags, File, FileMode, FsNode, Stat, S_IFREG}, userland::buffer::{UserBuffer, UserBufferMut, UserBufferReader, UserBufferWriter}, util::{lock::IrqMutex, KResult}, }; pub struct InitRamFsFile { pub name: IrqMutex, pub(super) data: IrqMutex>, pub(super) stat: IrqMutex, } impl InitRamFsFile { pub fn new(name: String, inode_no: usize) -> InitRamFsFile { InitRamFsFile { name: IrqMutex::new(name), data: IrqMutex::new(Vec::new()), stat: IrqMutex::new(Stat { inode_no, mode: FileMode::new(S_IFREG | 0o644), ..Stat::zeroed() }), } } } impl FsNode for InitRamFsFile { fn get_name(&self) -> String { self.name.lock().clone() } } impl File for InitRamFsFile { fn read(&self, offset: usize, buf: UserBufferMut<'_>, _options: &OpenFlags) -> KResult { let lock = self.data.lock(); if offset > lock.len() { return Ok(0); } let mut writer = UserBufferWriter::from_buf(buf); writer.write_bytes(&lock[offset..]) } fn write(&self, offset: usize, buf: UserBuffer<'_>, options: &OpenFlags) -> KResult { let mut reader = UserBufferReader::from_buf(buf); let mut data = self.data.lock(); let data_len = data.len(); if data.is_empty() { data.extend_from_slice(&vec![0u8; offset + reader.remaining_len()]); } else if offset + reader.remaining_len() < isize::MAX as usize && (offset + reader.remaining_len() > data_len || options.contains(OpenFlags::O_APPEND)) { data.push(0u8); } reader.read_bytes(&mut data[offset..]) } fn stat(&self) -> KResult { Ok(*self.stat.lock()) } } ================================================ FILE: src/fs/initramfs/mod.rs ================================================ use core::iter::Peekable; use alloc::{ string::{String, ToString}, sync::{Arc, Weak}, vec::Vec, }; use spin::Once; use crate::{ fs::{ DirRef, FileMode, FileSize, FsNode, INode, Stat, initramfs::{ dir::{DirInner, InitRamFsDir}, file::InitRamFsFile, symlink::InitRamFsSymlink, }, path::{Components, Path, PathBuf}, }, kbail, kerror, util::{IrqMutex, KResult, align_up}, }; use self::root::RootFs; pub mod dir; pub mod file; pub mod root; pub mod symlink; pub struct ByteParser<'a> { buffer: &'a [u8], current: usize, } impl<'a> ByteParser<'a> { pub fn new(buffer: &'a [u8]) -> ByteParser<'a> { ByteParser { buffer, current: 0 } } pub fn remaining(&self) -> &[u8] { &self.buffer[self.current..] } pub fn remaining_len(&self) -> usize { self.buffer.len() - self.current } pub fn skip(&mut self, len: usize) -> KResult<()> { if self.current + len > self.buffer.len() { kbail!("skip(): out of bounds"); } self.current += len; Ok(()) } pub fn skip_until_alignment(&mut self, align: usize) -> KResult<()> { let next = align_up(self.current, align); if next > self.buffer.len() { kbail!("skip_until_alignment(): out of bounds"); } self.current = next; Ok(()) } pub fn consume_bytes(&mut self, len: usize) -> KResult<&'a [u8]> { if self.current + len > self.buffer.len() { kbail!("consume_bytes(): out of bounds"); } self.current += len; Ok(&self.buffer[self.current - len..self.current]) } } fn parse_str_field(bytes: &[u8]) -> KResult<&str> { core::str::from_utf8(bytes).map_err(|_e| kerror!("parse_str_field(): UTF-8 parsing error")) } fn parse_hex_field(bytes: &[u8]) -> KResult { usize::from_str_radix(parse_str_field(bytes)?, 16) .map_err(|_e| kerror!("parse_hex_field(): int parsing error")) } pub static INITRAM_FS: Once> = Once::new(); pub fn init() -> KResult<()> { INITRAM_FS.call_once(|| { let image = include_bytes!(concat!( env!("CARGO_MANIFEST_DIR"), "/initramfs/initramfs.img" )); if image.is_empty() { panic!("initramfs not embedded"); } log::info!("Parsing initramfs..."); Arc::new(InitRamFs::parse(image.as_slice()).expect("error parsing initramfs")) }); Ok(()) } pub fn get_root() -> Option<&'static RootFs> { Some(&INITRAM_FS.get()?.root) } pub struct InitRamFs { root: RootFs, } impl InitRamFs { pub fn parse(fs_image: &[u8]) -> KResult { let mut image = ByteParser::new(fs_image); let mut n_files = 0; let mut loaded_size = 0; let root = Arc::new(InitRamFsDir::new(String::new(), 2)); loop { if image.remaining_len() == 0 { break; } let magic = image.consume_bytes(6).and_then(parse_hex_field)?; if magic != 0x070701 { log::error!( "initramfs: invalid magic (expected {:#x}, got {:#x})", 0x070701, magic ); kbail!("parse(): invalid magic"); } let ino = parse_hex_field(image.consume_bytes(8)?)?; let mode = FileMode::new(parse_hex_field(image.consume_bytes(8)?)? as u32); let _uid = parse_hex_field(image.consume_bytes(8)?)?; let _gid = parse_hex_field(image.consume_bytes(8)?)?; let _nlink = parse_hex_field(image.consume_bytes(8)?)?; let _mtime = parse_hex_field(image.consume_bytes(8)?)?; let filesize = parse_hex_field(image.consume_bytes(8)?)?; let _dev_major = parse_hex_field(image.consume_bytes(8)?)?; let _dev_minor = parse_hex_field(image.consume_bytes(8)?)?; image.skip(16)?; let path_len = parse_hex_field(image.consume_bytes(8)?)?; if path_len == 0 { kbail!("parse(): path_len is 0"); } image.skip(8)?; let mut path = parse_str_field(image.consume_bytes(path_len - 1)?)?; if path.starts_with("./") { path = &path[1..]; } if path == "TRAILER!!!" { break; } if path.is_empty() { kbail!("parse(): empty path"); } image.skip(1)?; image.skip_until_alignment(4)?; let components = Path::new(path).components().peekable(); fn walk( mut components_peekable: Peekable, dir: DirRef, ) -> Option<(DirRef, String)> { let next = components_peekable.next(); next?; if components_peekable.peek().is_none() { return Some((dir, next.unwrap().to_string())); } let dir_clone = dir.clone(); if let Ok(child) = dir.lookup(next.unwrap()) { if let INode::Dir(next_dir) = child { return walk(components_peekable, next_dir.clone()); } else { return Some((dir_clone, child.get_name())); } } None } if path == "." { image.skip_until_alignment(4)?; continue; } let walk_result = walk(components, root.clone()); let (parent_dir, filename) = if let Some((parent, name)) = walk_result { (parent, name) } else { image.consume_bytes(filesize)?; image.skip_until_alignment(4)?; continue; }; let data = image.consume_bytes(filesize)?; if mode.is_symbolic_link() { let inode = INode::Symlink(Arc::new(InitRamFsSymlink { name: filename.clone(), dst: PathBuf::from(core::str::from_utf8(data).unwrap()), stat: Stat { inode_no: ino, mode, ..Stat::zeroed() }, })); parent_dir.insert(inode); } else if mode.is_directory() { let inode = INode::Dir(Arc::new(InitRamFsDir { parent: Weak::new(), inner: IrqMutex::new(DirInner { children: Vec::new(), stat: Stat { inode_no: ino, mode, ..Stat::zeroed() }, name: filename.clone(), }), })); parent_dir.insert(inode); } else if mode.is_regular_file() { let file = InitRamFsFile { name: IrqMutex::new(filename.clone()), data: IrqMutex::new(data.to_vec()), stat: IrqMutex::new(Stat { inode_no: ino, mode, size: FileSize(filesize as isize), ..Stat::zeroed() }), }; parent_dir.insert(INode::File(Arc::new(file))); } image.skip_until_alignment(4)?; n_files += 1; loaded_size += data.len(); } log::info!( "initramfs: found {} files taking up {} bytes", n_files, loaded_size ); Ok(InitRamFs { root: RootFs::new(root), }) } } ================================================ FILE: src/fs/initramfs/root.rs ================================================ use alloc::{borrow::ToOwned, boxed::Box, string::String, sync::Arc}; use crate::{ fs::{ path::{Path, PathComponent}, pipe::PIPE_FS, DirRef, INode, }, kbail, util::KResult, }; use super::dir::InitRamFsDir; const MAX_SYMLINK_FOLLOW_DEPTH: usize = 20; #[derive(Clone)] pub struct RootFs { root_path: PathComponent, cwd_path: PathComponent, } impl RootFs { pub fn new(root: Arc) -> RootFs { let root_path = PathComponent { parent_dir: None, name: Arc::new(String::new()), inode: INode::Dir(root), }; RootFs { root_path: root_path.clone(), cwd_path: root_path, } } pub fn cwd_path(&self) -> &PathComponent { &self.cwd_path } pub fn root_dir(&self) -> DirRef { self.root_path.inode.as_dir().unwrap().clone() } pub fn cwd_dir(&self) -> DirRef { self.cwd_path.inode.as_dir().unwrap().clone() } pub fn chdir(&mut self, path: &Path) -> KResult<()> { self.cwd_path = self.lookup_path(path, true)?; Ok(()) } pub fn lookup(&self, path: &Path, follow_symlinks: bool) -> KResult { if path.is_pipe() { return PIPE_FS.lookup(path).map(INode::Pipe); } self.lookup_path(path, follow_symlinks) .map(|cmp| cmp.inode.clone()) } pub fn lookup_path(&self, path: &Path, follow_symlinks: bool) -> KResult { if path.is_empty() { kbail!(ENOENT, "lookup_path(): empty path"); } let lookup_from = if path.is_absolute() { self.root_path.clone() } else { self.cwd_path.clone() }; self.do_lookup_path( &lookup_from, path, follow_symlinks, MAX_SYMLINK_FOLLOW_DEPTH, ) } fn do_lookup_path( &self, lookup_from: &PathComponent, path: &Path, follow_symlinks: bool, symlink_follow_limit: usize, ) -> KResult { let mut parent = lookup_from.clone(); let mut components = path.components().peekable(); while let Some(name) = components.next() { let path_comp = match name { "." => continue, ".." => parent .parent_dir .as_deref() .unwrap_or(&self.root_path) .clone(), _ => { let inode = parent.inode.as_dir()?.lookup(name)?; PathComponent { parent_dir: Some(Box::new(parent.clone())), name: Arc::new(name.to_owned()), inode, } } }; if components.peek().is_some() { parent = match &path_comp.inode { INode::Dir(_) => path_comp, INode::Pipe(_) => { unreachable!("Pipes should be contained in PipeFs, not RootFs") } INode::Symlink(link) if follow_symlinks => { if symlink_follow_limit == 0 { kbail!(ELOOP, "lookup_path(): maximum symlink depth reached"); } let dst = link.link_location()?; let follow_from = if dst.is_absolute() { &self.root_path } else { &parent }; let dst_path = self.do_lookup_path( follow_from, &dst, follow_symlinks, symlink_follow_limit - 1, )?; match dst_path.inode { INode::Dir(_) => dst_path, _ => { kbail!(ENOTDIR, "lookup_path(): not a directory"); } } } INode::Symlink(_) => { kbail!(ENOTDIR, "lookup_path(): not a directory"); } INode::File(_) => { kbail!(ENOTDIR, "lookup_path(): not a directory"); } } } else { match &path_comp.inode { INode::Symlink(link) if follow_symlinks => { if symlink_follow_limit == 0 { kbail!(ELOOP, "lookup_path(): maximum symlink depth reached"); } let dst = link.link_location()?; let follow_from = if dst.is_absolute() { &self.root_path } else { &parent }; return self.do_lookup_path( follow_from, &dst, follow_symlinks, symlink_follow_limit - 1, ); } _ => return Ok(path_comp), } } } Ok(parent) } } ================================================ FILE: src/fs/initramfs/symlink.rs ================================================ use alloc::string::String; use crate::{ fs::{path::PathBuf, FsNode, Stat, Symlink}, util::KResult, }; pub struct InitRamFsSymlink { pub(crate) name: String, pub(crate) dst: PathBuf, pub(crate) stat: Stat, } impl Symlink for InitRamFsSymlink { fn link_location(&self) -> KResult { Ok(self.dst.clone()) } fn stat(&self) -> KResult { Ok(self.stat) } } impl FsNode for InitRamFsSymlink { fn get_name(&self) -> String { self.name.clone() } } ================================================ FILE: src/fs/mod.rs ================================================ use core::sync::atomic::{AtomicUsize, Ordering}; use alloc::{string::String, sync::Arc}; use bitflags::bitflags; use crate::{ kerror, task::wait_queue::WaitQueue, userland::buffer::{UserBuffer, UserBufferMut}, util::{ctypes::c_short, KResult}, }; use self::{opened_file::OpenFlags, path::PathBuf, pipe::Pipe}; pub mod devfs; pub mod initramfs; pub mod opened_file; pub mod path; pub mod pipe; pub type FileRef = Arc; pub type DirRef = Arc; pub type SymlinkRef = Arc; pub static POLL_WAIT_QUEUE: WaitQueue = WaitQueue::new(); pub fn alloc_inode_no() -> usize { // Inode #1 is reserved for the root dir. static NEXT_INODE_NO: AtomicUsize = AtomicUsize::new(2); NEXT_INODE_NO.fetch_add(1, Ordering::AcqRel) } bitflags! { #[derive(Debug)] pub struct PollStatus: c_short { const POLLIN = 0x001; const POLLPRI = 0x002; const POLLOUT = 0x004; const POLLERR = 0x008; const POLLHUP = 0x010; const POLLNVAL = 0x020; const POLLRDNORM = 0x040; const POLLRDBAND = 0x080; const POLLWRNORM = 0x100; const POLLWRBAND = 0x200; } } #[derive(Debug, Copy, Clone, Eq, PartialEq)] #[repr(u8)] #[non_exhaustive] pub enum FileType { Directory = 4, Regular = 8, Link = 10, } /// for readdir(3) pub struct DirEntry { pub inode_no: usize, pub file_type: FileType, pub name: String, } /// The device file's ID. #[derive(Debug, Copy, Clone)] #[repr(transparent)] pub struct DevId(usize); /// The number of hard links. #[derive(Debug, Copy, Clone)] #[repr(transparent)] pub struct NLink(usize); /// The file size in bytes. #[derive(Debug, Copy, Clone)] #[repr(transparent)] pub struct FileSize(pub isize); /// The user ID. #[derive(Debug, Copy, Clone)] #[repr(transparent)] pub struct UId(u32); /// The Group ID. #[derive(Debug, Copy, Clone)] #[repr(transparent)] pub struct GId(u32); /// The size in bytes of a block file file system I/O operations. #[derive(Debug, Copy, Clone)] #[repr(transparent)] pub struct BlockSize(isize); /// The number of blocks. #[derive(Debug, Copy, Clone)] #[repr(transparent)] pub struct BlockCount(isize); #[derive(Debug, Copy, Clone)] #[repr(transparent)] pub struct Time(isize); #[derive(Debug, Copy, Clone)] #[repr(C, packed)] pub struct Stat { pub dev: DevId, pub inode_no: usize, pub nlink: NLink, pub mode: FileMode, pub uid: UId, pub gid: GId, pub pad0: u32, pub rdev: DevId, pub size: FileSize, pub blksize: BlockSize, pub blocks: BlockCount, pub atime: Time, pub mtime: Time, pub ctime: Time, } impl Stat { pub fn zeroed() -> Stat { Stat { dev: DevId(0), inode_no: 0, mode: FileMode(0), nlink: NLink(0), uid: UId(0), gid: GId(0), pad0: 0, rdev: DevId(0), size: FileSize(0), blksize: BlockSize(0), blocks: BlockCount(0), atime: Time(0), mtime: Time(0), ctime: Time(0), } } } pub const S_IFMT: u32 = 0o170000; pub const S_IFCHR: u32 = 0o020000; pub const S_IFDIR: u32 = 0o040000; pub const S_IFREG: u32 = 0o100000; pub const S_IFLNK: u32 = 0o120000; pub const O_ACCMODE: u32 = 0o3; // FIXME: OpenFlags also define these values. #[allow(unused)] pub const O_RDONLY: u32 = 0o0; pub const O_WRONLY: u32 = 0o1; pub const O_RDWR: u32 = 0o2; #[derive(Debug, Clone, Copy)] #[repr(transparent)] pub struct FileMode(u32); impl FileMode { pub fn new(val: u32) -> FileMode { FileMode(val) } pub fn access_mode(self) -> u32 { self.0 & O_ACCMODE } pub fn is_directory(self) -> bool { (self.0 & S_IFMT) == S_IFDIR } pub fn is_regular_file(self) -> bool { (self.0 & S_IFMT) == S_IFREG } pub fn is_symbolic_link(self) -> bool { (self.0 & S_IFMT) == S_IFLNK } } pub trait FsNode { fn get_name(&self) -> String; } pub trait File: FsNode { /// `open(2)`. fn open(&self, _options: &OpenFlags) -> KResult> { Ok(None) } /// `stat(2)`. fn stat(&self) -> KResult { Err(kerror!(EBADF, "stat(): not implemented")) } /// `readlink(2)`. fn readlink(&self) -> KResult { // "EINVAL - The named file is not a symbolic link." -- readlink(2) Err(kerror!(EINVAL, "readlink(): not a symbolic link")) } /// `poll(2)` and `select(2)`. fn poll(&self) -> KResult { Err(kerror!(EBADF, "poll(): not implemented")) } /// `ioctl(2)`. fn ioctl(&self, _cmd: usize, _arg: usize) -> KResult { Err(kerror!(EBADF, "ioctl(): not implemented")) } /// `read(2)`. fn read(&self, _offset: usize, _buf: UserBufferMut, _options: &OpenFlags) -> KResult { Err(kerror!(EBADF, "read(): not implemented")) } /// `write(2)`. fn write(&self, _offset: usize, _buf: UserBuffer<'_>, _options: &OpenFlags) -> KResult { Err(kerror!(EBADF, "write(): not implemented")) } } pub trait Symlink: FsNode { fn link_location(&self) -> KResult; fn stat(&self) -> KResult; fn fsync(&self) -> KResult<()> { Ok(()) } } pub trait Directory: FsNode { fn insert(&self, inode: INode); /// Looks for an existing file. fn lookup(&self, name: &str) -> KResult; /// `stat(2)`. fn stat(&self) -> KResult; /// `fsync(2)`. fn fsync(&self) -> KResult<()> { Ok(()) } /// `readlink(2)`. fn readlink(&self) -> KResult { // "EINVAL - The named file is not a symbolic link." -- readlink(2) Err(kerror!(EINVAL, "readlink(): not a symbolic link")) } fn readdir(&self, index: usize) -> KResult>; fn unlink(&self, name: &str) -> KResult<()>; } #[derive(Clone)] pub enum INode { File(FileRef), Dir(DirRef), Symlink(SymlinkRef), Pipe(Arc), } impl INode { pub fn is_file(&self) -> bool { matches!(self, INode::File(_)) } pub fn is_dir(&self) -> bool { matches!(self, INode::Dir(_)) } pub fn is_symlink(&self) -> bool { matches!(self, INode::Symlink(_)) } pub fn is_pipe(&self) -> bool { matches!(self, INode::Pipe(_)) } pub fn as_file(&self) -> KResult<&FileRef> { match self { INode::File(file) => Ok(file), _ => Err(kerror!(EINVAL, "as_file(): not a file")), } } pub fn stat(&self) -> KResult { match self { INode::Dir(d) => d.stat(), INode::File(d) => d.stat(), INode::Symlink(d) => d.stat(), INode::Pipe(p) => p.stat(), } } pub fn as_dir(&self) -> KResult<&DirRef> { match self { INode::Dir(d) => Ok(d), _ => Err(kerror!(ENOTDIR, "as_dir(): not a directory")), } } pub fn as_symlink(&self) -> KResult<&SymlinkRef> { match self { INode::Symlink(s) => Ok(s), _ => Err(kerror!(EINVAL, "as_symlink(): not a symbolic link")), } } pub fn as_pipe(&self) -> KResult<&Arc> { match self { INode::Pipe(p) => Ok(p), _ => Err(kerror!(EINVAL, "as_pipe(): not a pipe")), } } } impl FsNode for INode { fn get_name(&self) -> String { match self { INode::Dir(d) => d.get_name(), INode::File(f) => f.get_name(), INode::Symlink(l) => l.get_name(), INode::Pipe(p) => p.get_name(), } } } ================================================ FILE: src/fs/opened_file.rs ================================================ use core::{borrow::BorrowMut, ops::Deref}; use alloc::{sync::Arc, vec::Vec}; use atomic_refcell::AtomicRefCell; use bitflags::bitflags; use crossbeam_utils::atomic::AtomicCell; use crate::{ kerror, userland::buffer::{UserBuffer, UserBufferMut}, util::{ctypes::c_int, error::KResult}, }; use super::{ devfs::socket::Socket, path::PathComponent, pipe::{Pipe, PIPE_FS}, DirEntry, DirRef, FileRef, FsNode, INode, PollStatus, }; const FD_MAX: c_int = 1024; bitflags! { #[derive(Clone, Copy, Debug)] pub struct OpenFlags: i32 { const O_RDONLY = 0o0; const O_WRONLY = 0o1; const O_RDWR = 0o2; const O_CREAT = 0o0100; const O_EXCL = 0o0200; const O_NOCTTY = 0o0400; const O_TRUNC = 0o01000; const O_APPEND = 0o02000; const O_NONBLOCK = 0o04000; const O_DSYNC = 0o010000; const O_SYNC = 0o04010000; const O_RSYNC = 0o04010000; const O_DIRECTORY = 0o0200000; const O_NOFOLLOW = 0o0400000; const O_CLOEXEC = 0o02000000; const O_ASYNC = 0o020000; const O_DIRECT = 0o040000; const O_LARGEFILE = 0o0100000; const O_NOATIME = 0o01000000; const O_PATH = 0o010000000; const O_TMPFILE = 0o020200000; } } pub type FileDesc = c_int; #[repr(C)] pub struct OpenedFile { path: PathComponent, pos: AtomicCell, options: AtomicRefCell, } impl OpenedFile { pub fn new(path: PathComponent, options: OpenFlags, pos: usize) -> OpenedFile { OpenedFile { path, pos: AtomicCell::new(pos), options: AtomicRefCell::new(options), } } pub fn as_file(&self) -> KResult<&FileRef> { self.path.inode.as_file() } pub fn as_dir(&self) -> KResult<&DirRef> { self.path.inode.as_dir() } pub fn pos(&self) -> usize { self.pos.load() } pub fn options(&self) -> OpenFlags { *self.options.borrow() } pub fn path(&self) -> &PathComponent { &self.path } pub fn inode(&self) -> &INode { &self.path.inode } pub fn read(&self, buf: UserBufferMut) -> KResult { let options = self.options(); let pos = self.pos(); let read_len = self.as_file()?.read(pos, buf, &options)?; self.pos.fetch_add(read_len); Ok(read_len) } pub fn write(&self, buf: UserBuffer) -> KResult { let options = self.options(); let pos = self.pos(); let written_len = self.as_file()?.write(pos, buf, &options)?; self.pos.fetch_add(written_len); Ok(written_len) } pub fn set_close_on_exec(&self, close_on_exec: bool) { self.options() .borrow_mut() .set(OpenFlags::O_CLOEXEC, close_on_exec); } pub fn set_flags(&self, flags: OpenFlags) -> KResult<()> { *self.options.borrow_mut() = flags; Ok(()) } pub fn get_flags(&self) -> OpenFlags { *self.options.borrow() } pub fn poll(&self) -> KResult { self.as_file()?.poll() } pub fn ioctl(&self, cmd: usize, arg: usize) -> KResult { self.as_file()?.ioctl(cmd, arg) } pub fn readdir(&self) -> KResult> { let pos = self.pos(); let entry = self.as_dir()?.readdir(pos)?; self.pos.fetch_add(1); Ok(entry) } pub fn lseek(&self, offset: usize, whence: LseekWhence) -> KResult { // We perform this check before we know we need something from the inode // because this should only be called on files anyway. let file = self.inode().as_file()?; match whence { LseekWhence::Set => self.pos.store(offset), LseekWhence::Cur => _ = self.pos.fetch_add(offset), LseekWhence::End => self.pos.store(file.stat()?.size.0 as usize - offset), }; Ok(self.pos()) } } #[repr(usize)] pub enum LseekWhence { Set = 0, Cur = 1, End = 2, } impl From for LseekWhence { fn from(value: usize) -> Self { match value { 0 => LseekWhence::Set, 1 => LseekWhence::Cur, 2 => LseekWhence::End, _ => panic!("Invalid LseekWhence"), } } } #[derive(Clone)] pub struct LocalOpenedFile { opened_file: Arc, } impl Deref for LocalOpenedFile { type Target = OpenedFile; fn deref(&self) -> &Self::Target { &self.opened_file } } #[derive(Clone)] pub struct OpenedFileTable { files: Vec>, prev_fd: i32, } impl Default for OpenedFileTable { fn default() -> Self { Self::new() } } impl OpenedFileTable { pub fn new() -> OpenedFileTable { OpenedFileTable { files: Vec::new(), prev_fd: 1, } } pub fn get(&self, fd: FileDesc) -> KResult { match self.files.get(fd as usize) { Some(Some(file)) => Ok(file.clone()), _ => Err(kerror!(EBADF, "get(): file not opened")), } } pub fn open(&mut self, path: PathComponent, options: OpenFlags) -> KResult { let fd = self.alloc_fd(None)?; self.open_with_fd(fd, OpenedFile::new(path, options, 0).into(), options)?; Ok(fd) } pub fn open_with_fd( &mut self, fd: FileDesc, mut opened_file: Arc, options: OpenFlags, ) -> KResult<()> { if let INode::File(file) = &opened_file.path.inode { if let Some(new_inode) = file.open(&options)? { opened_file = Arc::new(OpenedFile::new( PathComponent { parent_dir: opened_file.path.parent_dir.clone(), name: opened_file.path.name.clone(), inode: INode::File(new_inode), }, options, 0, )); } } match self.files.get_mut(fd as usize) { Some(Some(_)) => return Err(kerror!(EBADF, "open_with_fd(): file already opened")), Some(entry @ None) => { *entry = Some(LocalOpenedFile { opened_file }); } None if fd >= FD_MAX => { return Err(kerror!( ENFILE, "open_with_fd(): maximum file descriptor reached" )) } None => { self.files.resize(fd as usize + 1, None); self.files[fd as usize] = Some(LocalOpenedFile { opened_file }) } } Ok(()) } fn alloc_fd(&mut self, gte: Option) -> KResult { let (mut i, gte) = match gte { Some(gte) => (gte, gte), None => ((self.prev_fd + 1) % FD_MAX, 0), }; while i != self.prev_fd && i >= gte { if matches!(self.files.get(i as usize), Some(None) | None) { self.prev_fd = i; return Ok(i); } i = (i + 1) % FD_MAX; } Err(kerror!(ENFILE, "alloc_fd(): cannot alloc file descriptor")) } pub fn close_all(&mut self) { self.files.clear() } pub fn close_cloexec_files(&mut self) { for opened_file in &mut self.files { if matches!( opened_file, Some(LocalOpenedFile { // close_on_exec: true, // opened_file .. }) ) { let cloexec = opened_file .as_ref() .unwrap() .opened_file .options() .contains(OpenFlags::O_CLOEXEC); if cloexec { *opened_file = None; } } } } pub fn close(&mut self, fd: FileDesc) -> KResult<()> { match self.files.get_mut(fd as usize) { Some(opened_file) => *opened_file = None, _ => return Err(kerror!(EBADF, "close(): file not opened")), } Ok(()) } pub fn dup(&mut self, fd: FileDesc, gte: Option, options: OpenFlags) -> KResult { let file = self.get(fd)?; let new_fd = self.alloc_fd(gte)?; self.open_with_fd(new_fd, file.opened_file, options)?; Ok(new_fd) } pub fn dup2(&mut self, old_fd: FileDesc, new_fd: FileDesc) -> KResult { let old_file = self.get(old_fd)?; let options = old_file.options(); self.open_with_fd(new_fd, old_file.opened_file, options)?; Ok(new_fd) } pub fn open_socket(&mut self, domain: usize, typ: usize, protocol: usize) -> KResult { let fd = self.alloc_fd(None)?; let socket = Arc::new(Socket { domain: domain.try_into()?, typ: typ.try_into()?, protocol: protocol.try_into()?, id: Socket::alloc_id(), }); self.open_with_fd( fd, OpenedFile::new( PathComponent { parent_dir: None, name: Arc::new(socket.get_name()), inode: INode::File(socket.clone()), }, OpenFlags::empty(), 0, ) .into(), OpenFlags::empty(), )?; Ok(fd) } pub fn open_pipe(&mut self, options: OpenFlags) -> KResult> { let write_fd = self.alloc_fd(None)?; let read_fd = self.alloc_fd(Some(write_fd + 1))?; let pipe = Arc::new(Pipe::new(read_fd, write_fd)); PIPE_FS.insert(pipe.clone()); self.files.resize(read_fd as usize + 1, None); self.open_with_fd( write_fd, OpenedFile::new( PathComponent { parent_dir: None, name: Arc::new(pipe.get_name()), inode: INode::Pipe(pipe.clone()), }, options, 0, ) .into(), options, )?; self.open_with_fd( read_fd, OpenedFile::new( PathComponent { parent_dir: None, name: Arc::new(pipe.get_name()), inode: INode::Pipe(pipe.clone()), }, options, 0, ) .into(), options, )?; Ok(pipe) } } ================================================ FILE: src/fs/path.rs ================================================ use alloc::{borrow::ToOwned, boxed::Box, string::String, sync::Arc}; use super::INode; #[derive(Debug, Eq, PartialEq, Hash)] pub struct Path { path: str, } impl Path { pub fn new(path: &str) -> &Path { let path = if path == "/" { path } else { path.trim_end_matches('/') }; unsafe { &*(path as *const str as *const Path) } } pub fn as_str(&self) -> &str { &self.path } pub fn is_empty(&self) -> bool { self.path.is_empty() } pub fn is_absolute(&self) -> bool { self.path.starts_with('/') && !self .components() .any(|comp| matches!(comp, ".." | "." | "")) } pub fn is_pipe(&self) -> bool { self.path.starts_with("pipe:") } pub fn pipe_name(&self) -> Option<&str> { if self.is_pipe() { Some(&self.path[5..]) } else { None } } pub fn components(&self) -> Components<'_> { let path = if self.path.starts_with('/') { &self.path[1..] } else { &self.path }; Components { path } } pub fn parent_and_basename(&self) -> Option<(&Path, &str)> { if &self.path == "/" { return None; } if let Some(slash_idx) = self.path.rfind('/') { let parent_dir = if slash_idx == 0 { Path::new("/") } else { Path::new(&self.path[..slash_idx]) }; let basename = &self.path[(slash_idx + 1)..]; Some((parent_dir, basename)) } else { Some((Path::new("."), &self.path)) } } } impl AsRef for Path { fn as_ref(&self) -> &Path { self } } impl AsRef for str { fn as_ref(&self) -> &Path { Path::new(self) } } impl core::fmt::Display for Path { fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { write!(f, "{}", &self.path) } } pub struct Components<'a> { path: &'a str, } impl<'a> Iterator for Components<'a> { type Item = &'a str; fn next(&mut self) -> Option { if self.path.is_empty() { return None; } let (path_str, next_start) = match self.path.find('/') { Some(slash_idx) => (&self.path[..slash_idx], slash_idx + 1), None => (self.path, self.path.len()), }; self.path = &self.path[next_start..]; Some(path_str) } } #[derive(Debug, Clone, Eq, PartialEq, Hash)] pub struct PathBuf { path: String, } impl PathBuf { pub fn new() -> PathBuf { PathBuf { path: String::new(), } } pub fn as_path(&self) -> &Path { Path::new(&self.path) } pub fn pop(&mut self) { if let Some((index, _)) = self.path.char_indices().rfind(|(_, ch)| *ch == '/') { self.path.truncate(index); } } pub fn push>(&mut self, path: P) { let path = path.as_ref(); let path_str = if path.as_str() == "/" { "/" } else { path.as_str().trim_end_matches('/') }; if path.is_absolute() { self.path = path_str.to_owned() } else { if self.path != "/" { self.path.push('/'); } self.path.push_str(path_str) } } } impl Default for PathBuf { fn default() -> Self { PathBuf::new() } } impl core::ops::Deref for PathBuf { type Target = Path; fn deref(&self) -> &Self::Target { self.as_path() } } impl AsRef for PathBuf { fn as_ref(&self) -> &Path { self.as_path() } } impl From<&Path> for PathBuf { fn from(value: &Path) -> Self { PathBuf { path: value.path.to_owned(), } } } impl From for PathBuf { fn from(value: String) -> Self { // TODO: check if this is a valid path PathBuf { path: value } } } impl From<&str> for PathBuf { fn from(value: &str) -> Self { // TODO: check if this is a valid path PathBuf { path: value.to_owned(), } } } #[derive(Clone)] pub struct PathComponent { pub parent_dir: Option>, pub name: Arc, pub inode: INode, } impl PathComponent { pub fn resolve_abs_path(&self) -> PathBuf { let path = if self.parent_dir.is_some() { let mut path = self.name.as_ref().to_owned(); let mut parent_dir = self.parent_dir.clone(); while let Some(ref path_comp) = parent_dir { path = path_comp.name.as_ref().to_owned() + "/" + &path; parent_dir = path_comp.parent_dir.clone(); } debug_assert!(path.starts_with('/')); path } else { "/".to_owned() }; PathBuf::from(path) } } ================================================ FILE: src/fs/pipe.rs ================================================ use alloc::{format, sync::Arc, vec::Vec}; use crate::{ kerror, task::wait_queue::WaitQueue, userland::buffer::{UserBuffer, UserBufferMut, UserBufferReader, UserBufferWriter}, util::{ringbuffer::RingBuffer, IrqMutex, KResult}, }; use super::{opened_file::FileDesc, path::Path, File, FsNode}; pub static PIPE_FS: PipeFs = PipeFs::new(); pub struct Pipe { wait_queue: WaitQueue, ringbuffer: Arc>>, read_fd: FileDesc, write_fd: FileDesc, } impl Pipe { pub fn new(read_fd: FileDesc, write_fd: FileDesc) -> Self { Self { wait_queue: WaitQueue::new(), ringbuffer: Arc::new(IrqMutex::new(RingBuffer::new())), read_fd, write_fd, } } pub fn read_pipe(&self, buf: UserBufferMut<'_>) -> KResult { let mut writer = UserBufferWriter::from_buf(buf); let mut ringbuffer = self.wait_queue.sleep_signalable_until(None, || { let ringbuffer = self.ringbuffer.try_lock(); if let Ok(ringbuffer) = ringbuffer { if ringbuffer.is_readable() { Ok(Some(ringbuffer)) } else { Ok(None) } } else { Ok(None) } })?; while let Some(byte) = ringbuffer.pop() { writer.write(byte)?; } Ok(writer.written_len()) } pub fn write_pipe(&self, buf: UserBuffer<'_>) -> KResult { let mut reader = UserBufferReader::from_buf(buf); let mut ringbuffer = self.wait_queue.sleep_signalable_until(None, || { let ringbuffer = self.ringbuffer.try_lock(); if let Ok(ringbuffer) = ringbuffer { if ringbuffer.is_writable() { Ok(Some(ringbuffer)) } else { Ok(None) } } else { Ok(None) } })?; while let Ok(byte) = reader.read::() { ringbuffer.push(byte).ok(); } Ok(reader.read_len()) } pub fn read_fd(&self) -> FileDesc { self.read_fd } pub fn write_fd(&self) -> FileDesc { self.write_fd } } impl FsNode for Pipe { fn get_name(&self) -> alloc::string::String { format!("pipe_{}_{}", self.write_fd, self.read_fd) } } impl File for Pipe { fn read( &self, _offset: usize, buf: UserBufferMut, _options: &super::opened_file::OpenFlags, ) -> KResult { self.read_pipe(buf) } fn write( &self, _offset: usize, buf: UserBuffer<'_>, _options: &super::opened_file::OpenFlags, ) -> KResult { self.write_pipe(buf) } } pub struct PipeFs { pipes: IrqMutex>>, } impl PipeFs { #[allow(clippy::new_without_default)] pub const fn new() -> Self { Self { pipes: IrqMutex::new(Vec::new()), } } pub fn insert(&self, pipe: Arc) { self.pipes.lock().push(pipe); } pub fn lookup(&self, path: &Path) -> KResult> { self.pipes .lock() .iter() .find(|pipe| pipe.get_name() == path.pipe_name().unwrap()) .cloned() .ok_or(kerror!(ENOENT, "pipe does not exist")) } } ================================================ FILE: src/god_mode.rs ================================================ use alloc::{borrow::ToOwned, collections::VecDeque, string::String, sync::Arc}; use spin::Once; use x86_64::instructions::interrupts; use crate::{ mem::{ addr::PhysAddr, allocator::{GLOBAL_ALLOC, KERNEL_FRAME_ALLOCATOR}, consts::{KERNEL_HEAP_SIZE, PAGE_SIZE}, }, task::{get_scheduler, Task, TaskId}, util::{align_down, BlockingMutex}, }; pub static GOD_MODE_TASK: Once> = Once::new(); pub static GOD_MODE_FIFO: Once>> = Once::new(); pub fn init() { GOD_MODE_FIFO.call_once(|| BlockingMutex::new(VecDeque::new())); let sched = get_scheduler(); GOD_MODE_TASK.call_once(|| Task::new_kernel(sched, god_mode_repl, true)); sched.push_runnable(GOD_MODE_TASK.get().unwrap().clone(), false); } fn read_cmd() -> String { let mut cmd = String::new(); loop { let mut lock = GOD_MODE_FIFO.get().unwrap().try_lock(); while let Ok(Some(ch)) = lock.as_mut().map(|lock| lock.pop_front()) { if ch == b'\n' || ch == b'\r' { drop(lock); return cmd; } let st = core::str::from_utf8(&[ch]).unwrap().to_owned(); cmd.push_str(&st); serial1_print!("{}", st); } drop(lock); interrupts::enable_and_hlt(); } } pub fn god_mode_repl() { loop { serial1_print!("\ngodmode > "); let cmd = read_cmd(); let mut args = cmd.split_whitespace(); let cmd = if let Some(cmd) = args.next() { cmd } else { continue; }; serial1_println!(); log::warn!("God said: {}", cmd); match cmd { "f" | "frames" => { serial1_println!("Dumping free physical memory."); let fa = KERNEL_FRAME_ALLOCATOR.get().unwrap().lock(); let mut total_space_pages = 0; for area in fa.free_regions() { total_space_pages += area.size_in_pages(); serial1_println!("Free chunk at {:?}", area); } serial1_println!("Total free pages: {}", total_space_pages); serial1_println!("Total free bytes: {}", total_space_pages * PAGE_SIZE); } "vm" | "vmem" => { let pid = if let Some(Ok(pid)) = args.next().map(|arg| arg.parse()) { TaskId::new(pid) } else { serial1_println!("Invalid argument. Specify a PID to inspect vmem of."); continue; }; let sched = get_scheduler(); let task = if let Some(task) = sched.find_task(pid) { task } else { serial1_println!("Invalid argument. PID not found."); continue; }; serial1_println!( "Dumping virtual memory of pid {}. Check serial0 (stdio).", pid.as_usize() ); task.vmem().lock().log(); } "x" | "examine" => { let start = if let Some(Ok(start)) = args.next().map(|arg| usize::from_str_radix(arg, 16)) { align_down(start, PAGE_SIZE) } else { serial1_println!("Invalid argument. Specify the address to dump the frame of."); continue; }; let ptr = PhysAddr::new(start).as_hhdm_virt().as_raw_ptr::(); let max_i = PAGE_SIZE / core::mem::size_of::(); serial1_println!("Dumping frame at {:#x}.", start); for i in 0..max_i / 4 { let i = i * 4; serial1_print!("{:#016x} >> ", start + i * core::mem::size_of::()); for j in 0..4 { let offset = i + j; serial1_print!("{:016x} ", unsafe { ptr.add(offset).read_volatile() }); } serial1_println!(); } } "h" | "heap" => { let lock = GLOBAL_ALLOC.try_lock(); if let Some(lock) = lock { serial1_println!("Kernel heap size: {:#08x}", KERNEL_HEAP_SIZE); serial1_println!( "Kernel heap usage (actual): {:#08x} ({:.4}%)", lock.stats_alloc_actual(), lock.stats_alloc_actual() as f64 / KERNEL_HEAP_SIZE as f64 * 100.0 ); serial1_println!( "Kernel heap usage (user): {:#08x} ({:.4}%)", lock.stats_alloc_user(), lock.stats_alloc_user() as f64 / KERNEL_HEAP_SIZE as f64 * 100.0 ); } else { serial1_println!("Error locking global allocator."); } } _ => {} } } } ================================================ FILE: src/graphics/mod.rs ================================================ use core::ops::Add; use alloc::{boxed::Box, vec::Vec}; use embedded_graphics::{ mono_font::{ascii::FONT_8X13, MonoFont, MonoTextStyle}, pixelcolor::Rgb888, prelude::*, text::{Alignment, Text}, }; use limine::response::FramebufferResponse; use spin::Once; use crate::{ mem::addr::VirtAddr, util::{IrqMutex, IrqMutexGuard, KResult}, vga_text::{BUFFER_HEIGHT, BUFFER_WIDTH}, }; const FONT: MonoFont = FONT_8X13; pub struct FrameBuffer { back_buffer: Box<[u32]>, start_addr: VirtAddr, width: usize, height: usize, bpp: usize, text_buf: [[Option; BUFFER_WIDTH]; BUFFER_HEIGHT], text_cursor_x: usize, text_cursor_y: usize, text_fgcolor: Rgb888, } impl FrameBuffer { pub fn width(&self) -> usize { self.width } pub fn height(&self) -> usize { self.height } pub fn bpp(&self) -> usize { self.bpp } pub fn render_text_buf(&mut self) { let mut out = Vec::new(); for line in 0..BUFFER_HEIGHT { 'inner: for col in 0..BUFFER_WIDTH { if let Some(ch) = self.text_buf[line][col] { out.push(ch); } else { out.push(b'\n'); break 'inner; } } } let mono_font = MonoTextStyle::new(&FONT, self.text_fgcolor); Text::with_alignment( core::str::from_utf8(&out).unwrap(), self.bounding_box().top_left + Point::new( FONT.character_size.width as i32, FONT.character_size.height as i32, ), mono_font, Alignment::Left, ) .draw(self) .unwrap(); } pub fn clear_pixels(&mut self) { self.clear(Rgb888::new(0, 0, 0)).unwrap(); } pub fn frame_mut(&mut self) -> &mut [u32] { &mut self.back_buffer } pub fn present(&mut self) { unsafe { self.start_addr .as_raw_ptr_mut::() .copy_from_nonoverlapping(self.back_buffer.as_ptr(), self.width * self.height); } } pub fn write_byte(&mut self, byte: u8) { match byte { 0x8 => self.backspace(), b'\n' => self.new_line(), b'\r' => self.text_cursor_x = 0, byte => { if self.text_cursor_x >= BUFFER_WIDTH - 1 { self.new_line(); } let row = self.text_cursor_y; let col = self.text_cursor_x; self.text_buf[row][col] = Some(byte); self.move_right(); } } self.cursor_color_hook(); } fn cursor_color_hook(&mut self) {} pub fn backspace(&mut self) { let row = self.text_cursor_y; let col = self.text_cursor_x.saturating_sub(1); self.text_buf[row][col] = None; self.text_cursor_x = col; self.cursor_color_hook(); } pub fn write_string(&mut self, s: &str) { for byte in s.bytes() { self.write_byte(byte) } } fn new_line(&mut self) { if self.text_cursor_y >= BUFFER_HEIGHT - 1 { for row in 1..BUFFER_HEIGHT { for col in 0..BUFFER_WIDTH { let character = self.text_buf[row][col]; self.text_buf[row - 1][col] = character; } } self.text_cursor_y = BUFFER_HEIGHT - 1; self.clear_row(self.text_cursor_y); self.text_cursor_x = 0; } else { self.text_cursor_y += 1; self.text_cursor_x = 0; } self.cursor_color_hook(); } fn clear_row(&mut self, row: usize) { for col in 0..BUFFER_WIDTH { self.text_buf[row][col] = None; } self.cursor_color_hook(); } fn clear_until_end(&mut self) { for col in self.text_cursor_x..BUFFER_WIDTH { self.text_buf[self.text_cursor_y][col] = None; } for row in self.text_cursor_y + 1..BUFFER_HEIGHT { self.clear_row(row); } self.cursor_color_hook(); } fn clear_until_beginning(&mut self) { for col in 0..self.text_cursor_x { self.text_buf[self.text_cursor_y][col] = None; } for row in 0..self.text_cursor_y - 1 { self.clear_row(row); } self.cursor_color_hook(); } fn clear_until_eol(&mut self) { for col in self.text_cursor_x..BUFFER_WIDTH { self.text_buf[self.text_cursor_y][col] = None; } self.cursor_color_hook(); } fn clear_from_bol(&mut self) { for col in 0..self.text_cursor_x { self.text_buf[self.text_cursor_y][col] = None; } self.cursor_color_hook(); } fn clear_line(&mut self) { self.clear_row(self.text_cursor_y); } fn clear_all(&mut self) { for row in 0..BUFFER_HEIGHT { self.clear_row(row) } self.cursor_color_hook(); } fn move_up(&mut self) { let new_y = self.text_cursor_y.saturating_sub(1); // let mut new_x = self.text_cursor_x; // while new_x > 0 && self.text_buf[new_y][new_x] == b' ' { // new_x -= 1; // } self.text_cursor_y = new_y; // self.text_cursor_x = new_x; self.cursor_color_hook(); } fn move_down(&mut self) { let new_y = self.text_cursor_y.add(1).min(BUFFER_HEIGHT - 1); // let mut new_x = self.text_cursor_x; // while new_x > 0 && self.text_buf[new_y][new_x] == b' ' { // new_x -= 1; // } self.text_cursor_y = new_y; // self.text_cursor_x = new_x; self.cursor_color_hook(); } fn move_left(&mut self) { self.text_cursor_x = self.text_cursor_x.saturating_sub(1); self.cursor_color_hook(); } fn move_right(&mut self) { self.text_cursor_x = self.text_cursor_x.add(1).min(BUFFER_WIDTH - 1); self.cursor_color_hook(); } } impl core::fmt::Write for FrameBuffer { fn write_str(&mut self, s: &str) -> core::fmt::Result { self.write_string(s); Ok(()) } } impl DrawTarget for FrameBuffer { type Color = Rgb888; type Error = core::convert::Infallible; fn draw_iter(&mut self, pixels: I) -> Result<(), Self::Error> where I: IntoIterator>, { for Pixel(coord, color) in pixels.into_iter() { let (x, y) = coord.into(); if (0..self.width as i32).contains(&x) && (0..self.height as i32).contains(&y) { let index: usize = x as usize + y as usize * self.width; self.back_buffer[index] = color.into_storage(); } } Ok(()) } fn clear(&mut self, color: Self::Color) -> Result<(), Self::Error> { self.back_buffer.fill(color.into_storage()); Ok(()) } } impl OriginDimensions for FrameBuffer { fn size(&self) -> embedded_graphics::prelude::Size { Size::new(self.width as u32, self.height as u32) } } pub static FRAMEBUFFER: Once> = Once::new(); pub fn fb<'a>() -> IrqMutexGuard<'a, FrameBuffer> { FRAMEBUFFER.get().unwrap().lock() } pub fn clear_screen() { fb().clear_all() } pub fn backspace() { fb().backspace() } pub fn set_cursor_x(x: usize) { fb().text_cursor_x = x.min(BUFFER_WIDTH - 1); } pub fn set_cursor_y(y: usize) { fb().text_cursor_y = y.min(BUFFER_HEIGHT - 1); } pub fn set_cursor_xy(xy: (usize, usize)) { set_cursor_x(xy.0.min(BUFFER_WIDTH - 1)); set_cursor_y(xy.1.min(BUFFER_HEIGHT - 1)); } pub fn cursor_xy() -> (usize, usize) { let fb = fb(); (fb.text_cursor_x, fb.text_cursor_y) } pub fn write_byte(byte: u8) { fb().write_byte(byte); } pub fn clear_until_end() { fb().clear_until_end(); } pub fn clear_until_beginning() { fb().clear_until_beginning(); } pub fn clear_from_bol() { fb().clear_from_bol(); } pub fn clear_until_eol() { fb().clear_until_eol(); } pub fn clear_line() { fb().clear_line(); } pub fn move_up() { fb().move_up(); } pub fn move_down() { fb().move_down(); } pub fn move_left() { fb().move_left(); } pub fn move_right() { fb().move_right(); } pub fn render_text_buf() { fb().clear_pixels(); fb().render_text_buf(); fb().present(); } #[macro_export] macro_rules! fb_print { ($($arg:tt)*) => ({ $crate::graphics::_fb_print(format_args!($($arg)*)); $crate::serial::_print2(format_args!($($arg)*)); }); } #[macro_export] macro_rules! fb_println { () => ($crate::fb_print!("\n")); ($($arg:tt)*) => ($crate::fb_print!("{}\n", format_args!($($arg)*))); } #[doc(hidden)] pub fn _fb_print(args: core::fmt::Arguments) { use core::fmt::Write; x86_64::instructions::interrupts::without_interrupts(|| { FRAMEBUFFER.get().unwrap().lock().write_fmt(args).unwrap(); }); } pub fn init(fb_tag: &FramebufferResponse) -> KResult<()> { let fb_tag = fb_tag.framebuffers().next().unwrap(); let fb_base = fb_tag.addr() as usize; log::debug!("FB addr: {:#x}", fb_base); let framebuf = FrameBuffer { back_buffer: alloc::vec![0u32; fb_tag.width() as usize * fb_tag.height() as usize] .into_boxed_slice(), start_addr: VirtAddr::new(fb_base), width: fb_tag.width() as usize, height: fb_tag.height() as usize, bpp: fb_tag.bpp() as usize, text_buf: [[None; BUFFER_WIDTH]; BUFFER_HEIGHT], text_cursor_x: 0, text_cursor_y: 0, text_fgcolor: Rgb888::WHITE, }; FRAMEBUFFER.call_once(|| IrqMutex::new(framebuf)); fb().clear_pixels(); clear_screen(); Ok(()) } ================================================ FILE: src/logging.rs ================================================ use log::Level; use log::Log; use crate::serial0_print; use crate::serial0_println; use crate::task::SCHEDULER; struct KaDOSLogger; impl Log for KaDOSLogger { fn enabled(&self, _metadata: &log::Metadata) -> bool { true } fn log(&self, record: &log::Record) { match record.level() { Level::Debug => serial0_print!("\x1b[1;36m"), // cyan Level::Error => serial0_print!("\x1b[1;31m"), // red Level::Info => serial0_print!("\x1b[1;32m"), // green Level::Warn => serial0_print!("\x1b[1;33m"), // yellow Level::Trace => serial0_print!("\x1b[1;37m"), // white } if let Some(sched) = SCHEDULER.get() { if let Some(current) = sched.current_task_opt() { serial0_print!( "[{}]\t[{}] - {}", record.level(), current.pid().as_usize(), record.args() ); } else { serial0_print!("[{}]\t{}", record.level(), record.args()); } } else { serial0_print!("[{}]\t{}", record.level(), record.args()); } serial0_println!("\x1b[0m"); // reset color } fn flush(&self) {} } struct KaDOSProfiler; impl embedded_profiling::EmbeddedProfiler for KaDOSProfiler { fn read_clock(&self) -> embedded_profiling::EPInstant { let uptime = crate::arch::x86_64::time::get_uptime_ns(); embedded_profiling::EPInstant::from_ticks(uptime as u32) } fn log_snapshot(&self, snapshot: &embedded_profiling::EPSnapshot) { crate::serial0_println!("{}", snapshot); } } pub fn init() { log::set_logger(&KaDOSLogger).expect("error setting logger"); let level = if let Some(level) = option_env!("RUST_LOG") .or(option_env!("KADOS_LOG")) .or(option_env!("K4DOS_LOG")) { match level { "error" => log::LevelFilter::Error, "warn" => log::LevelFilter::Warn, "info" => log::LevelFilter::Info, "debug" => log::LevelFilter::Debug, "trace" => log::LevelFilter::Trace, _ => log::LevelFilter::Info, } } else { log::LevelFilter::Info }; log::set_max_level(level); unsafe { embedded_profiling::set_profiler(&KaDOSProfiler).expect("error setting profiler") }; } ================================================ FILE: src/main.rs ================================================ #![no_std] #![no_main] #![feature( lang_items, abi_x86_interrupt, ptr_internals, slice_pattern, map_try_insert, iter_advance_by, alloc_error_handler )] #![allow(internal_features)] #![allow(clippy::missing_safety_doc, clippy::uninlined_format_args)] #![deny(unsafe_op_in_unsafe_fn)] // #![warn(clippy::unwrap_used)] extern crate alloc; #[macro_use] pub mod vga_text; #[macro_use] pub mod serial; pub mod arch; pub mod backtrace; pub mod fs; pub mod logging; pub mod mem; pub mod task; pub mod userland; pub mod util; #[macro_use] pub mod graphics; pub mod god_mode; use mem::addr::VirtAddr; use spin::Once; use x86_64::instructions::hlt; pub static PHYSICAL_OFFSET: Once = Once::new(); #[inline] pub fn phys_offset() -> VirtAddr { unsafe { VirtAddr::new_unchecked(*PHYSICAL_OFFSET.get().unwrap()) } } #[unsafe(no_mangle)] pub extern "C" fn start() -> ! { arch::arch_main(); hcf(); } pub fn hcf() -> ! { loop { hlt(); core::hint::spin_loop(); } } ================================================ FILE: src/mem/addr.rs ================================================ use core::fmt::{self}; use core::mem::align_of; use core::ops::*; use core::ptr::NonNull; use crate::kbail; use crate::task::current_task; use crate::util::{align_down, align_up, KResult}; use super::consts::PAGE_SIZE; #[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] #[repr(transparent)] pub struct PhysAddr { addr: usize, } #[inline] pub const fn canonicalize_physaddr(addr: usize) -> usize { addr & 0x000F_FFFF_FFFF_FFFF } #[inline] pub const fn is_canonical_physaddr(addr: usize) -> bool { canonicalize_physaddr(addr) == addr } #[inline] pub const fn canonicalize_virtaddr(addr: usize) -> usize { ((addr << 16) as isize >> 16) as usize } #[inline] pub const fn is_canonical_virtaddr(addr: usize) -> bool { canonicalize_virtaddr(addr) == addr } impl PhysAddr { #[inline] pub const unsafe fn new_unchecked(addr: usize) -> Self { Self { addr } } #[inline] pub const fn new(addr: usize) -> Self { assert!( is_canonical_physaddr(addr), "PhysAddr::new(): non-canonical address" ); unsafe { Self::new_unchecked(addr) } } #[inline] pub const fn null() -> Self { unsafe { Self::new_unchecked(0) } } #[inline] pub const fn is_null(&self) -> bool { self.addr == 0 } #[inline] pub const fn is_canonical(&self) -> bool { is_canonical_physaddr(self.addr) } #[inline] pub const fn value(&self) -> usize { self.addr } #[inline] pub fn as_hhdm_virt(&self) -> VirtAddr { VirtAddr::new(crate::phys_offset().value() + self.value()) } #[inline] pub fn is_aligned(&self, align: usize) -> bool { self.addr % align == 0 } pub const fn const_add(&self, offset: usize) -> Self { Self::new(self.addr + offset) } pub const fn const_sub(&self, offset: usize) -> Self { Self::new(self.addr - offset) } } impl fmt::Debug for PhysAddr { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { f.debug_tuple("PhysAddr") .field(&format_args!("{:#x}", self.addr)) .finish() } } impl fmt::Binary for PhysAddr { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { fmt::Binary::fmt(&self.addr, f) } } impl fmt::LowerHex for PhysAddr { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { fmt::LowerHex::fmt(&self.addr, f) } } impl fmt::Octal for PhysAddr { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { fmt::Octal::fmt(&self.addr, f) } } impl fmt::UpperHex for PhysAddr { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { fmt::UpperHex::fmt(&self.addr, f) } } impl fmt::Pointer for PhysAddr { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { fmt::Pointer::fmt(&(self.addr as *const ()), f) } } impl Add for PhysAddr { type Output = Self; fn add(self, rhs: usize) -> Self::Output { PhysAddr::new(self.addr + rhs) } } impl AddAssign for PhysAddr { fn add_assign(&mut self, rhs: usize) { *self = *self + rhs; } } impl Sub for PhysAddr { type Output = Self; fn sub(self, rhs: usize) -> Self::Output { PhysAddr::new(self.addr.checked_sub(rhs).unwrap()) } } impl SubAssign for PhysAddr { fn sub_assign(&mut self, rhs: usize) { *self = *self - rhs; } } impl Sub for PhysAddr { type Output = usize; fn sub(self, rhs: PhysAddr) -> Self::Output { self.value().checked_sub(rhs.value()).unwrap() } } #[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] #[repr(transparent)] pub struct VirtAddr { addr: usize, } impl VirtAddr { #[inline] pub const unsafe fn new_unchecked(addr: usize) -> Self { Self { addr } } #[inline] pub const fn new(addr: usize) -> Self { // assert!(is_canonical_virtaddr(addr)); unsafe { Self::new_unchecked(canonicalize_virtaddr(addr)) } } #[inline] pub const fn null() -> Self { Self { addr: 0 } } #[inline] pub const fn is_null(&self) -> bool { self.addr == 0 } #[inline] pub const fn is_canonical(&self) -> bool { is_canonical_virtaddr(self.addr) } #[inline] pub const fn value(&self) -> usize { self.addr } #[inline] pub const fn const_add(&self, offset: usize) -> Self { Self::new(self.addr + offset) } #[inline] pub const fn const_sub(&self, offset: usize) -> Self { Self::new(self.addr - offset) } #[inline] pub const fn as_raw_ptr(&self) -> *const T { self.value() as *const T } #[inline] pub const fn as_raw_ptr_mut(&self) -> *mut T { self.value() as *mut T } #[inline] pub unsafe fn deref(&self) -> KResult<&T> { self.align_ok::()?; Ok(unsafe { &*self.as_raw_ptr() }) } #[inline] pub unsafe fn deref_mut(&mut self) -> KResult<&mut T> { self.align_ok::()?; Ok(unsafe { &mut *self.as_raw_ptr_mut() }) } #[inline] pub const fn as_ptr(self) -> UnsafePtr { UnsafePtr::new(self) } #[inline] pub fn as_hhdm_phys(&self) -> PhysAddr { PhysAddr::new(self.value() - crate::phys_offset().value()) } pub const fn align_ok(&self) -> KResult<()> { if self.addr == 0 { kbail!(EFAULT, "align_ok(): null VirtAddr"); } if self.addr % align_of::() != 0 { kbail!(EFAULT, "align_ok(): unaligned VirtAddr"); } if !is_canonical_virtaddr(self.addr) { kbail!(EFAULT, "align_ok(): non-canonical VirtAddr"); } Ok(()) } pub fn user_ok(&self) -> KResult<()> { if self.addr == 0 { kbail!(EFAULT, "user_ok(): null VirtAddr"); } if !is_canonical_virtaddr(self.addr) { kbail!(EFAULT, "user_ok(): non-canonical VirtAddr"); } if self >= &crate::mem::consts::MAX_LOW_VADDR { kbail!(EFAULT, "user_ok(): VirtAddr in kernel memory"); } Ok(()) } pub unsafe fn read(&self) -> KResult { self.align_ok::()?; Ok(unsafe { core::ptr::read(self.as_raw_ptr::().cast()) }) } pub unsafe fn read_volatile(&self) -> KResult { self.align_ok::()?; Ok(unsafe { core::ptr::read_volatile(self.as_raw_ptr::().cast()) }) } pub unsafe fn read_user(&self) -> KResult { self.align_ok::()?; self.user_ok()?; let _guard = current_task().arch_mut().address_space.temporarily_switch(); Ok(unsafe { core::ptr::read_volatile(self.as_raw_ptr::().cast()) }) } pub unsafe fn read_bytes(&self, buf: &mut [u8]) -> KResult { self.align_ok::()?; unsafe { core::ptr::copy(self.as_raw_ptr(), buf.as_mut_ptr(), buf.len()) }; Ok(buf.len()) } pub unsafe fn read_bytes_user(&self, buf: &mut [u8]) -> KResult { self.align_ok::()?; (*self + buf.len()).user_ok()?; let _guard = current_task().arch_mut().address_space.temporarily_switch(); unsafe { core::ptr::copy(self.as_raw_ptr(), buf.as_mut_ptr(), buf.len()) }; Ok(buf.len()) } pub unsafe fn write(&self, t: T) -> KResult<()> { self.align_ok::()?; unsafe { core::ptr::write(self.as_raw_ptr_mut(), t) }; Ok(()) } pub unsafe fn write_volatile(&self, t: T) -> KResult<()> { self.align_ok::()?; unsafe { core::ptr::write_volatile(self.as_raw_ptr_mut(), t) }; Ok(()) } pub unsafe fn write_user(&self, t: T) -> KResult<()> { self.align_ok::()?; (*self + size_of::()).user_ok()?; let _guard = current_task().arch_mut().address_space.temporarily_switch(); unsafe { core::ptr::write_volatile(self.as_raw_ptr_mut(), t) }; Ok(()) } pub unsafe fn write_bytes(&self, bytes: &[u8]) -> KResult { if self.is_null() { kbail!(EFAULT, "write_bytes(): null VirtAddr"); } unsafe { core::slice::from_raw_parts_mut(self.as_raw_ptr_mut(), bytes.len()) .copy_from_slice(bytes) }; Ok(bytes.len()) } pub unsafe fn write_bytes_user(&self, bytes: &[u8]) -> KResult { if self.is_null() { kbail!(EFAULT, "write_bytes_user(): null VirtAddr"); } self.user_ok()?; (*self + bytes.len()).user_ok()?; let _guard = current_task().arch_mut().address_space.temporarily_switch(); unsafe { core::slice::from_raw_parts_mut(self.as_raw_ptr_mut(), bytes.len()) .copy_from_slice(bytes) }; Ok(bytes.len()) } pub unsafe fn fill(&self, value: u8, len: usize) -> KResult { if self.is_null() { kbail!(EFAULT, "fill(): null VirtAddr"); } unsafe { (self.value() as *mut u8).write_bytes(value, len) }; Ok(len) } #[inline] pub const fn align_down(&self, align: usize) -> Self { VirtAddr::new(align_down(self.addr, align)) } #[inline] pub const fn align_up(&self, align: usize) -> Self { VirtAddr::new(align_up(self.addr, align)) } pub fn p4_index(&self) -> usize { ((self.addr / PAGE_SIZE) >> 27) & 0x1FF } pub fn p3_index(&self) -> usize { ((self.addr / PAGE_SIZE) >> 18) & 0x1FF } pub fn p2_index(&self) -> usize { ((self.addr / PAGE_SIZE) >> 9) & 0x1FF } pub fn p1_index(&self) -> usize { (self.addr / PAGE_SIZE) & 0x1FF } } impl fmt::Debug for VirtAddr { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { f.debug_tuple("VirtAddr") .field(&format_args!("{:#x}", self.addr)) .finish() } } impl fmt::Binary for VirtAddr { #[inline] fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { fmt::Binary::fmt(&self.addr, f) } } impl fmt::LowerHex for VirtAddr { #[inline] fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { fmt::LowerHex::fmt(&self.addr, f) } } impl fmt::Octal for VirtAddr { #[inline] fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { fmt::Octal::fmt(&self.addr, f) } } impl fmt::UpperHex for VirtAddr { #[inline] fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { fmt::UpperHex::fmt(&self.addr, f) } } impl fmt::Pointer for VirtAddr { #[inline] fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { fmt::Pointer::fmt(&(self.addr as *const ()), f) } } impl Add for VirtAddr { type Output = Self; #[inline] fn add(self, rhs: usize) -> Self::Output { VirtAddr::new(self.addr + rhs) } } impl AddAssign for VirtAddr { #[inline] fn add_assign(&mut self, rhs: usize) { self.addr += rhs; } } impl Sub for VirtAddr { type Output = Self; #[inline] fn sub(self, rhs: usize) -> Self::Output { VirtAddr::new(self.addr.checked_sub(rhs).unwrap()) } } impl SubAssign for VirtAddr { #[inline] fn sub_assign(&mut self, rhs: usize) { self.addr -= rhs; } } impl Sub for VirtAddr { type Output = usize; #[inline] fn sub(self, rhs: VirtAddr) -> Self::Output { self.value().checked_sub(rhs.value()).unwrap() } } /// Like a `NonNull`, but backed by a `VirtAddr`. #[derive(Debug, Clone, Copy)] #[repr(transparent)] pub struct UnsafePtr { addr: VirtAddr, _phantom: core::marker::PhantomData<*mut T>, } impl UnsafePtr { pub const fn new(addr: VirtAddr) -> Self { assert!(!addr.is_null(), "UnsafePtr::new(): null VirtAddr"); assert!( addr.value() % align_of::() == 0, "UnsafePtr::new(): unaligned VirtAddr" ); assert!( is_canonical_virtaddr(addr.value()), "UnsafePtr::new(): non-canonical VirtAddr" ); Self { addr, _phantom: core::marker::PhantomData, } } pub fn from_ref(data: &T) -> Self { Self::new(VirtAddr::new(data as *const T as usize)) } pub fn from_mut(data: &mut T) -> Self { Self::new(VirtAddr::new(data as *mut T as usize)) } pub fn from_raw_ptr(ptr: *mut T) -> Self { Self::new(VirtAddr::new(ptr as usize)) } pub fn as_raw_ptr(&self) -> *const T { self.addr.as_raw_ptr() } pub fn as_raw_ptr_mut(&self) -> *mut T { self.addr.as_raw_ptr_mut() } pub unsafe fn deref(&self) -> &T { unsafe { &*self.as_raw_ptr() } } pub unsafe fn deref_mut(&mut self) -> &mut T { unsafe { &mut *self.as_raw_ptr_mut() } } pub unsafe fn as_ref(&self) -> Ref<'_, T> { Ref::new(unsafe { self.deref() }) } pub unsafe fn as_mut(&mut self) -> Mut<'_, T> { Mut::new(unsafe { self.deref_mut() }) } pub fn cast(self) -> UnsafePtr { UnsafePtr::new(self.addr) } pub fn into_non_null(self) -> NonNull { assert!( !self.addr.is_null(), "UnsafePtr::as_non_null(): null VirtAddr" ); NonNull::new(self.as_raw_ptr_mut()).unwrap() } pub fn read(&self) -> KResult where T: Copy, { unsafe { self.addr.read() } } pub fn read_volatile(&self) -> KResult where T: Copy, { unsafe { self.addr.read_volatile() } } } impl fmt::Pointer for UnsafePtr { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { fmt::Pointer::fmt(&self.as_raw_ptr(), f) } } /// Like a `NonNull<[T]>`, but backed by a `VirtAddr`. #[derive(Debug, Clone, Copy)] pub struct UnsafeSlice { addr: VirtAddr, len: usize, _phantom: core::marker::PhantomData<*mut [T]>, } impl UnsafeSlice { pub fn new(addr: VirtAddr, len: usize) -> Self { assert!(!addr.is_null(), "UnsafeSlice::new(): null VirtAddr"); assert!( addr.value() % align_of::() == 0, "UnsafeSlice::new(): unaligned VirtAddr" ); assert!( is_canonical_virtaddr(addr.value()), "UnsafeSlice::new(): non-canonical VirtAddr" ); Self { addr, len, _phantom: core::marker::PhantomData, } } pub fn len(&self) -> usize { self.len } pub fn is_empty(&self) -> bool { self.len == 0 } pub fn as_ptr(&self) -> UnsafePtr { UnsafePtr::new(self.addr) } pub unsafe fn as_slice(&self) -> &[T] { unsafe { core::slice::from_raw_parts(self.addr.as_raw_ptr(), self.len) } } pub unsafe fn as_mut_slice(&mut self) -> &mut [T] { unsafe { core::slice::from_raw_parts_mut(self.addr.as_raw_ptr_mut(), self.len) } } pub fn into_raw_parts(self) -> (VirtAddr, usize) { (self.addr, self.len) } } /// Like a `&T`, but backed by a `VirtAddr`. #[derive(Debug, Clone, Copy)] pub struct Ref<'a, T> { addr: VirtAddr, _phantom: core::marker::PhantomData<&'a T>, } impl<'a, T> Ref<'a, T> { pub fn new(data: &'a T) -> Self { Self { addr: VirtAddr::new(data as *const T as usize), _phantom: core::marker::PhantomData, } } pub fn addr(&self) -> VirtAddr { self.addr } #[allow(clippy::should_implement_trait)] pub fn deref(&self) -> &'a T { unsafe { &*self.as_raw_ptr() } } pub fn as_ptr(&self) -> UnsafePtr { self.addr.as_ptr() } pub fn as_raw_ptr(&self) -> *const T { self.addr.as_raw_ptr() } pub fn as_raw_ptr_mut(&self) -> *mut T { self.addr.as_raw_ptr_mut() } } impl Deref for Ref<'_, T> { type Target = T; fn deref(&self) -> &Self::Target { >::deref(self) } } impl fmt::Pointer for Ref<'_, T> { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { fmt::Pointer::fmt(&self.as_raw_ptr(), f) } } /// Like a `&mut T`, but backed by a `VirtAddr`. #[derive(Debug)] pub struct Mut<'a, T> { addr: VirtAddr, _phantom: core::marker::PhantomData<&'a mut T>, } impl<'a, T> Mut<'a, T> { pub fn new(data: &'a mut T) -> Self { Self { addr: VirtAddr::new(data as *mut T as usize), _phantom: core::marker::PhantomData, } } pub fn addr(&self) -> VirtAddr { self.addr } pub fn as_ptr(&self) -> UnsafePtr { self.addr.as_ptr() } #[allow(clippy::should_implement_trait)] pub fn deref(&self) -> &'a T { unsafe { &*self.as_raw_ptr() } } #[allow(clippy::should_implement_trait)] pub fn deref_mut(&mut self) -> &'a mut T { unsafe { &mut *self.as_raw_ptr_mut() } } pub fn as_raw_ptr(&self) -> *const T { self.addr.as_raw_ptr() } pub fn as_raw_ptr_mut(&self) -> *mut T { self.addr.as_raw_ptr_mut() } } impl fmt::Pointer for Mut<'_, T> { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { fmt::Pointer::fmt(&self.as_raw_ptr(), f) } } impl Deref for Mut<'_, T> { type Target = T; fn deref(&self) -> &Self::Target { >::deref(self) } } impl DerefMut for Mut<'_, T> { fn deref_mut(&mut self) -> &mut Self::Target { >::deref_mut(self) } } /// Like a `&[T]`, but backed by a `VirtAddr`. #[derive(Debug, Clone, Copy)] pub struct Slice<'a, T> { addr: VirtAddr, len: usize, _phantom: core::marker::PhantomData<&'a [T]>, } impl<'a, T> Slice<'a, T> { pub fn new(data: &'a [T]) -> Self { Self { addr: VirtAddr::new(data.as_ptr() as usize), len: data.len(), _phantom: core::marker::PhantomData, } } pub unsafe fn from_raw_parts(addr: VirtAddr, len: usize) -> Self { addr.align_ok::().unwrap(); Self { addr, len, _phantom: core::marker::PhantomData, } } pub fn addr(&self) -> VirtAddr { self.addr } pub fn len(&self) -> usize { self.len } pub fn is_empty(&self) -> bool { self.len == 0 } pub fn as_ptr(&self) -> UnsafePtr { self.addr.as_ptr() } pub fn as_slice(&self) -> &'a [T] { unsafe { core::slice::from_raw_parts(self.addr.as_raw_ptr(), self.len) } } pub fn as_raw_ptr(&self) -> *const T { self.addr.as_raw_ptr() } pub fn as_raw_ptr_mut(&self) -> *mut T { self.addr.as_raw_ptr_mut() } } impl fmt::Pointer for Slice<'_, T> { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { fmt::Pointer::fmt(&self.as_raw_ptr(), f) } } impl Deref for Slice<'_, T> { type Target = [T]; fn deref(&self) -> &Self::Target { self.as_slice() } } impl Index for Slice<'_, T> { type Output = T; fn index(&self, index: usize) -> &Self::Output { &self.as_slice()[index] } } /// Like a `&mut [T]`, but backed by a `VirtAddr`. #[derive(Debug)] pub struct SliceMut<'a, T> { addr: VirtAddr, len: usize, _phantom: core::marker::PhantomData<&'a mut [T]>, } impl<'a, T> SliceMut<'a, T> { pub fn new(data: &'a mut [T]) -> Self { Self { addr: VirtAddr::new(data.as_mut_ptr() as usize), len: data.len(), _phantom: core::marker::PhantomData, } } pub unsafe fn from_raw_parts(addr: VirtAddr, len: usize) -> Self { addr.align_ok::().unwrap(); Self { addr, len, _phantom: core::marker::PhantomData, } } pub fn addr(&self) -> VirtAddr { self.addr } pub fn len(&self) -> usize { self.len } pub fn is_empty(&self) -> bool { self.len == 0 } pub fn as_ptr(&self) -> UnsafePtr { self.addr.as_ptr() } pub fn as_slice(&self) -> &'a [T] { unsafe { core::slice::from_raw_parts(self.addr.as_raw_ptr(), self.len) } } pub fn as_mut_slice(&mut self) -> &'a mut [T] { unsafe { core::slice::from_raw_parts_mut(self.addr.as_raw_ptr_mut(), self.len) } } pub fn as_raw_ptr(&self) -> *const T { self.addr.as_raw_ptr() } pub fn as_raw_ptr_mut(&self) -> *mut T { self.addr.as_raw_ptr_mut() } } impl fmt::Pointer for SliceMut<'_, T> { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { fmt::Pointer::fmt(&self.as_raw_ptr(), f) } } impl Deref for SliceMut<'_, T> { type Target = [T]; fn deref(&self) -> &Self::Target { self.as_slice() } } impl DerefMut for SliceMut<'_, T> { fn deref_mut(&mut self) -> &mut Self::Target { self.as_mut_slice() } } impl Index for SliceMut<'_, T> { type Output = T; fn index(&self, index: usize) -> &Self::Output { &self.as_slice()[index] } } impl IndexMut for SliceMut<'_, T> { fn index_mut(&mut self, index: usize) -> &mut Self::Output { &mut self.as_mut_slice()[index] } } ================================================ FILE: src/mem/addr_space.rs ================================================ use x86_64::{ registers::control::{Cr3, Cr3Flags}, structures::paging::{PageTableFlags, PhysFrame}, }; use crate::{util::KResult, vga_text}; use super::{ addr::{PhysAddr, VirtAddr}, allocator::alloc_kernel_frames, consts::PAGE_TABLE_ENTRIES, paging::{ mapper::Mapper, table::{active_table, PageTable}, units::{AllocatedFrames, Frame, FrameRange, MemoryUnit, Page}, }, }; pub struct AddressSpace { cr3: AllocatedFrames, } impl AddressSpace { pub fn new() -> KResult { let cr3 = unsafe { let frame = alloc_kernel_frames(1)?; let phys_addr = frame.start_address(); let mut virt_addr = phys_addr.as_hhdm_virt(); let page_table: &mut PageTable = virt_addr.deref_mut()?; let active_table = active_table(); // zero out lower half of virtual address space for i in 0..256 { page_table[i].set_unused(); } // copy kernel mappings for i in 256..512 { page_table[i] = active_table[i]; } frame }; let mut this = Self { cr3 }; this.with_mapper(|mut mapper| { mapper.map_to_single( Page::containing_address(unsafe { VirtAddr::new_unchecked(vga_text::VGA_BUFFER_START_PADDR) }), Frame::containing_address(unsafe { PhysAddr::new_unchecked(vga_text::VGA_BUFFER_START_PADDR) }), PageTableFlags::PRESENT | PageTableFlags::WRITABLE | PageTableFlags::NO_EXECUTE | PageTableFlags::USER_ACCESSIBLE, ) })?; Ok(this) } pub fn current() -> Self { let cr3 = Cr3::read().0.start_address().as_u64() as usize; let cr3 = PhysAddr::new(cr3); let cr3 = unsafe { AllocatedFrames::assume_allocated(FrameRange::new( Frame::containing_address(cr3), Frame::containing_address(cr3), )) }; Self { cr3 } } pub fn cr3(&self) -> PhysAddr { self.cr3.start_address() } pub fn switch(&self) { unsafe { Cr3::write( PhysFrame::containing_address(x86_64::PhysAddr::new( self.cr3.start_address().value() as u64, )), Cr3Flags::empty(), ); } } pub fn temporarily_switch(&self) -> TmpAddrSpaceGuard { let previous = Self::current(); self.switch(); TmpAddrSpaceGuard { previous } } pub fn with_mapper(&mut self, f: impl FnOnce(Mapper) -> R) -> R { let mut addr = self.cr3.start_address().as_hhdm_virt(); let table = unsafe { addr.deref_mut().unwrap() }; let mapper = Mapper::new(table); let _guard = self.temporarily_switch(); f(mapper) // _guard is dropped here } pub fn map_two( first: &mut Self, second: &mut Self, f: impl FnOnce(Mapper, Mapper) -> R, ) -> R { let mut addr = first.cr3.start_address().as_hhdm_virt(); let table = unsafe { addr.deref_mut().unwrap() }; let mapper = Mapper::new(table); let mut addr = second.cr3.start_address().as_hhdm_virt(); let table = unsafe { addr.deref_mut().unwrap() }; let other_mapper = Mapper::new(table); f(mapper, other_mapper) } pub fn is_active(&self) -> bool { self.cr3.start_address().value() == Cr3::read().0.start_address().as_u64() as usize } pub fn fork(&mut self, set_cow: bool) -> KResult { assert!(self.is_active(), "Can only fork the active address space"); let mut new = AddressSpace::new()?; let mut insert_flags = PageTableFlags::PRESENT | PageTableFlags::USER_ACCESSIBLE; if !set_cow { insert_flags |= PageTableFlags::WRITABLE; } Self::map_two(self, &mut new, |my_mapper, new_mapper| -> KResult<()> { let my_p4 = my_mapper.into_inner(); let new_p4 = new_mapper.into_inner(); for p4_idx in 0..256 { let my_entry = &my_p4[p4_idx]; if my_entry.is_unused() { continue; } let my_p3 = my_p4.next_table(p4_idx).unwrap(); let new_p3 = new_p4.next_table_create(p4_idx, insert_flags)?; for p3_idx in 0..PAGE_TABLE_ENTRIES { let my_entry = &my_p3[p3_idx]; if my_entry.is_unused() { continue; } let my_p2 = my_p3.next_table(p3_idx).unwrap(); let new_p2 = new_p3.next_table_create(p3_idx, insert_flags)?; for p2_idx in 0..PAGE_TABLE_ENTRIES { let my_entry = &my_p2[p2_idx]; if my_entry.is_unused() { continue; } let my_p1 = my_p2.next_table(p2_idx).unwrap(); let new_p1 = new_p2.next_table_create(p2_idx, insert_flags)?; for p1_idx in 0..PAGE_TABLE_ENTRIES { let my_entry = &my_p1[p1_idx]; if my_entry.is_unused() { continue; } let mut flags = my_entry.flags(); if set_cow { flags.remove(PageTableFlags::WRITABLE); } new_p1[p1_idx].set_frame(my_entry.frame().unwrap(), flags); } } } } Ok(()) })?; Ok(new) } } #[must_use = "TmpAddrSpaceGuard restores previous address space on drop"] pub struct TmpAddrSpaceGuard { previous: AddressSpace, } impl Drop for TmpAddrSpaceGuard { fn drop(&mut self) { self.previous.switch(); } } ================================================ FILE: src/mem/allocator.rs ================================================ use core::ops::{Index, IndexMut}; use alloc::vec::Vec; use arrayvec::ArrayVec; use buddy_system_allocator::LockedHeap; use limine::memory_map::{Entry, EntryType}; use spin::Once; use super::addr::{PhysAddr, VirtAddr}; use super::consts::{MAX_LOW_VADDR, MIN_HIGH_VADDR, PAGE_SIZE}; use super::paging::units::{ Allocated, AllocatedFrames, AllocatedPages, Frame, FrameRange, MemoryRange, MemoryUnit, Page, PageIndex, PageRange, }; use crate::kerror; use crate::util::{align_down, IrqMutex, KResult}; pub static KERNEL_FRAME_ALLOCATOR: Once> = Once::new(); pub static KERNEL_PAGE_ALLOCATOR: Once> = Once::new(); #[global_allocator] pub static GLOBAL_ALLOC: LockedHeap<32> = LockedHeap::new(); pub fn alloc_kernel_frames(count: usize) -> KResult { KERNEL_FRAME_ALLOCATOR .get() .ok_or(kerror!("KERNEL_FRAME_ALLOCATOR not initialized"))? .try_lock()? .allocate(count) } pub fn alloc_kernel_frames_at(start: Frame, count: usize) -> KResult { KERNEL_FRAME_ALLOCATOR .get() .ok_or(kerror!("KERNEL_FRAME_ALLOCATOR not initialized"))? .try_lock()? .allocate_at(start, count) } pub fn alloc_kernel_pages(count: usize) -> KResult { KERNEL_PAGE_ALLOCATOR .get() .ok_or(kerror!("KERNEL_PAGE_ALLOCATOR not initialized"))? .try_lock()? .allocate(count) } pub fn alloc_kernel_pages_at(start: Page, count: usize) -> KResult { KERNEL_PAGE_ALLOCATOR .get() .ok_or(kerror!("KERNEL_PAGE_ALLOCATOR not initialized"))? .try_lock()? .allocate_at(start, count) } pub fn free_kernel_frames(frames: &mut AllocatedFrames, merge: bool) -> KResult<()> { KERNEL_FRAME_ALLOCATOR .get() .ok_or(kerror!("KERNEL_FRAME_ALLOCATOR not initialized"))? .try_lock()? .free(frames, merge); Ok(()) } pub fn free_kernel_pages(pages: &mut AllocatedPages, merge: bool) -> KResult<()> { KERNEL_PAGE_ALLOCATOR .get() .ok_or(kerror!("KERNEL_PAGE_ALLOCATOR not initialized"))? .try_lock()? .free(pages, merge); Ok(()) } pub fn init(memmap: &[&Entry]) -> KResult<()> { let mut frame_alloc = FrameAllocator::new_static(); for entry in memmap .iter() .filter(|entry| entry.entry_type == EntryType::USABLE) { if (entry.length as usize) < PAGE_SIZE { continue; } let start = entry.base as usize; let end = start + entry.length as usize; let frames = FrameRange::new( Frame::containing_address(PhysAddr::new(start)), Frame::containing_address(PhysAddr::new(end)), ); unsafe { frame_alloc.insert_free_region(frames) }; } KERNEL_FRAME_ALLOCATOR.call_once(|| IrqMutex::new(frame_alloc)); let mut page_alloc = PageAllocator::new_static(); let pages = PageRange::new( Page::at_index(PageIndex(1)), Page::containing_address(MAX_LOW_VADDR - 1), ); unsafe { page_alloc.insert_free_region(pages) }; let pages = PageRange::new( Page::containing_address(MIN_HIGH_VADDR), Page::containing_address(VirtAddr::new(align_down(usize::MAX, PAGE_SIZE))), ); unsafe { page_alloc.insert_free_region(pages) }; KERNEL_PAGE_ALLOCATOR.call_once(|| IrqMutex::new(page_alloc)); Ok(()) } #[derive(Clone)] pub enum StaticListOrVec { StaticList(ArrayVec), Vec(Vec), } impl StaticListOrVec { pub const fn new_static() -> StaticListOrVec { StaticListOrVec::StaticList(ArrayVec::::new_const()) } pub const fn new_vec() -> StaticListOrVec { StaticListOrVec::Vec(Vec::new()) } pub fn push(&mut self, item: T) { match self { StaticListOrVec::StaticList(a) => a.push(item), StaticListOrVec::Vec(v) => v.push(item), } } pub fn remove(&mut self, index: usize) -> T { match self { StaticListOrVec::StaticList(a) => a.remove(index), StaticListOrVec::Vec(v) => v.remove(index), } } pub fn convert_to_vec(&mut self) { match self { StaticListOrVec::Vec(_v) => {} StaticListOrVec::StaticList(a) => { *self = StaticListOrVec::Vec(a.into_iter().map(|t| t.clone()).collect()); } } } pub fn iter(&self) -> impl Iterator { match self { StaticListOrVec::StaticList(a) => a.iter(), StaticListOrVec::Vec(v) => v.iter(), } } pub fn iter_mut(&mut self) -> impl Iterator { match self { StaticListOrVec::StaticList(a) => a.iter_mut(), StaticListOrVec::Vec(v) => v.iter_mut(), } } pub fn len(&self) -> usize { match self { StaticListOrVec::StaticList(a) => a.len(), StaticListOrVec::Vec(v) => v.len(), } } pub fn is_empty(&self) -> bool { match self { Self::StaticList(a) => a.is_empty(), Self::Vec(v) => v.is_empty(), } } pub fn swap(&mut self, a: usize, b: usize) { match self { StaticListOrVec::StaticList(l) => l.swap(a, b), StaticListOrVec::Vec(v) => v.swap(a, b), } } pub fn get(&self, index: usize) -> Option<&T> { match self { StaticListOrVec::StaticList(a) => a.get(index), StaticListOrVec::Vec(v) => v.get(index), } } } impl Index for StaticListOrVec { type Output = T; fn index(&self, index: usize) -> &Self::Output { match self { StaticListOrVec::StaticList(a) => &a[index], StaticListOrVec::Vec(v) => &v[index], } } } impl IndexMut for StaticListOrVec { fn index_mut(&mut self, index: usize) -> &mut Self::Output { match self { StaticListOrVec::StaticList(a) => &mut a[index], StaticListOrVec::Vec(v) => &mut v[index], } } } #[derive(Clone)] pub struct Allocator { free_regions: StaticListOrVec, 32>, } impl Allocator { pub fn new_static() -> Self { Self { free_regions: StaticListOrVec::new_static(), } } pub fn new_vec() -> Self { Self { free_regions: StaticListOrVec::new_vec(), } } pub fn convert_to_heap_allocated(&mut self) { self.free_regions.convert_to_vec(); } pub fn free_regions(&self) -> impl Iterator> { self.free_regions.iter() } pub unsafe fn insert_free_region(&mut self, range: MemoryRange) { self.free_regions.push(range); } pub fn allocate(&mut self, count: usize) -> KResult> { if count == 0 { return Err(kerror!("Cannot allocate 0 units")); } let mut best_fit = None; for region in self.free_regions.iter() { if region.size_in_pages() >= count { best_fit = Some(*region); break; } } let best_fit = best_fit.ok_or(kerror!("Out of memory"))?; let new_region = MemoryRange::new(best_fit.start, best_fit.start + count); if best_fit.size_in_pages() == count { self.free_regions.remove(0); } else { self.free_regions[0] = MemoryRange::new(best_fit.start + count, best_fit.end); } Ok(unsafe { Allocated::assume_allocated(new_region) }) } pub fn allocate_at(&mut self, start: T, count: usize) -> KResult> { if count == 0 { return Err(kerror!("Cannot allocate 0 units")); } let mut best_fit = None; for region in self.free_regions.iter() { if region.start() <= start && region.end() >= start + count { best_fit = Some(*region); break; } } let best_fit = best_fit.ok_or(kerror!("Out of memory"))?; let new_region = MemoryRange::new(start, start + count); if best_fit.start() == start { if best_fit.size_in_pages() == count { self.free_regions.remove(0); } else { self.free_regions[0] = MemoryRange::new(start + count, best_fit.end); } } else if best_fit.end() == start + count { self.free_regions[0] = MemoryRange::new(best_fit.start, start); } else { self.free_regions[0] = MemoryRange::new(best_fit.start, start); self.free_regions .push(MemoryRange::new(start + count, best_fit.end)); } Ok(unsafe { Allocated::assume_allocated(new_region) }) } pub fn free(&mut self, allocated: &mut Allocated, merge: bool) { let mut new_region = allocated.range; if merge { for region in self.free_regions.iter_mut() { if region.start() == new_region.end() { new_region = MemoryRange::new(new_region.start(), region.end()); *region = new_region; return; } else if region.end() == new_region.start() { new_region = MemoryRange::new(region.start(), new_region.end()); *region = new_region; return; } } } self.free_regions.push(new_region); } } pub type FrameAllocator = Allocator; pub type PageAllocator = Allocator; ================================================ FILE: src/mem/consts.rs ================================================ use super::addr::VirtAddr; pub const PAGE_SHIFT: usize = 12; pub const PAGE_SIZE: usize = 0x1000; pub const PAGE_TABLE_ENTRIES: usize = 512; pub const L4_SHIFT: usize = 39; pub const L3_SHIFT: usize = 30; pub const L2_SHIFT: usize = 21; pub const L1_SHIFT: usize = 12; pub const KERNEL_STACK_SIZE: usize = PAGE_SIZE * 32; pub const USER_STACK_SIZE: usize = PAGE_SIZE * 64; /// The maximum canonical virtual address in low (user) address space. /// All user virtual addresses are less than this value. pub const MAX_LOW_VADDR: VirtAddr = unsafe { VirtAddr::new_unchecked(0x0000_7000_0000_0000) }; /// The minimum canonical virtual address in high (kernel) address space. /// All kernel virtual addresses are greater than or equal to this value. pub const MIN_HIGH_VADDR: VirtAddr = unsafe { VirtAddr::new_unchecked(0xffff_8000_0000_0000) }; pub const USER_VALLOC_BASE: VirtAddr = unsafe { VirtAddr::new_unchecked(0x0000_000a_0000_0000) }; pub const USER_VALLOC_END: VirtAddr = unsafe { VirtAddr::new_unchecked(0x0000_0fff_0000_0000) }; pub const USER_STACK_TOP: VirtAddr = unsafe { VirtAddr::new_unchecked(0x0000_0fff_ffff_e000) }; pub const USER_STACK_BOTTOM: VirtAddr = USER_STACK_TOP.const_sub(USER_STACK_SIZE); pub const KERNEL_HEAP_START: VirtAddr = unsafe { VirtAddr::new_unchecked(0xFFFF_FE80_0000_0000) }; pub const KERNEL_HEAP_SIZE: usize = 1024 * 1024 * 1024; // 1024 MiB ================================================ FILE: src/mem/mod.rs ================================================ use alloc::sync::Arc; use paging::units::MemoryUnit; use spin::Once; use x86_64::structures::paging::PageTableFlags; use crate::{ kerror, task::{get_scheduler, Task}, util::{IrqMutex, KResult}, }; use self::{ addr_space::AddressSpace, allocator::{alloc_kernel_pages_at, GLOBAL_ALLOC}, consts::{KERNEL_HEAP_SIZE, KERNEL_HEAP_START, PAGE_SIZE}, paging::{ mapper::Mapper, units::{MappedPages, Page}, }, }; pub mod addr; pub mod addr_space; pub mod allocator; pub mod consts; pub mod paging; pub static KERNEL_ADDR_SPACE: Once> = Once::new(); pub fn remap_kernel() -> KResult<&'static IrqMutex> { let active = AddressSpace::current(); log::info!("Active page table at {:?}", active.cr3()); let new_space = AddressSpace::new()?; // and that's all we gotta do, because Offset Page Tables RULE! new_space.switch(); log::info!("Switched to new page table at {:?}", new_space.cr3()); Ok(KERNEL_ADDR_SPACE.call_once(|| IrqMutex::new(new_space))) } pub fn init_heap(kernel_mapper: &mut Mapper) -> KResult { let heap_ap = alloc_kernel_pages_at( Page::containing_address(KERNEL_HEAP_START), KERNEL_HEAP_SIZE / PAGE_SIZE, )?; let heap_mp = kernel_mapper.map( heap_ap, PageTableFlags::PRESENT | PageTableFlags::WRITABLE | PageTableFlags::NO_EXECUTE, )?; unsafe { GLOBAL_ALLOC.lock().add_to_heap( heap_mp.pages().start_address().value(), heap_mp.pages().end_address().value(), ); } Ok(heap_mp) } pub fn kernel_addr_space_scope() -> KResult { KernelAddrSpaceGuard::new() } pub fn with_kernel_addr_space R>(f: F) -> R { let _guard = kernel_addr_space_scope().expect("Failed to switch to kernel address space"); f() } #[must_use = "KernelAddrSpaceGuard restores previous address space on drop"] pub struct KernelAddrSpaceGuard { _private: (), current_task: Option>, } impl KernelAddrSpaceGuard { pub fn new() -> KResult { let _kernel_addr_space = KERNEL_ADDR_SPACE .get() .ok_or(kerror!("KERNEL_ADDR_SPACE not initialized"))? .try_lock()?; _kernel_addr_space.switch(); Ok(Self { _private: (), current_task: get_scheduler().current_task_opt(), }) } } impl Drop for KernelAddrSpaceGuard { fn drop(&mut self) { if let Some(task) = self.current_task.as_ref() { task.arch_mut().address_space.switch(); } } } ================================================ FILE: src/mem/paging/mapper.rs ================================================ use x86::tlb; use x86_64::structures::paging::PageTableFlags; use crate::{ mem::{ addr::{PhysAddr, VirtAddr}, allocator::alloc_kernel_frames, }, util::KResult, }; use super::{ table::PageTable, units::{AllocatedFrames, AllocatedPages, Frame, MappedPages, MemoryUnit, Page}, }; #[derive(Debug)] #[must_use = "Changes to page tables must be flushed or ignored."] pub struct PageFlush(Page); impl PageFlush { pub fn new(page: Page) -> Self { PageFlush(page) } pub fn ignore(self) {} pub fn flush(self) { unsafe { tlb::flush(self.0.start_address().value()) } } } #[derive(Debug)] pub struct Mapper<'a> { p4: &'a mut PageTable, } impl<'a> Mapper<'a> { pub fn new(p4: &'a mut PageTable) -> Self { Self { p4 } } pub fn into_inner(self) -> &'a mut PageTable { self.p4 } pub fn translate(&self, addr: VirtAddr) -> Option<(PhysAddr, PageTableFlags)> { let p3 = self.p4.next_table(addr.p4_index())?; let p2 = p3.next_table(addr.p3_index())?; let p1 = p2.next_table(addr.p2_index())?; let entry = p1[addr.p1_index()]; Some((entry.addr(), entry.flags())) } pub fn map_to_single( &mut self, page: Page, frame: Frame, flags: PageTableFlags, ) -> KResult<()> { let mut insert_flags = PageTableFlags::PRESENT | PageTableFlags::WRITABLE; if flags.contains(PageTableFlags::USER_ACCESSIBLE) { insert_flags |= PageTableFlags::USER_ACCESSIBLE; } let addr = page.start_address(); let p3 = self.p4.next_table_create(addr.p4_index(), insert_flags)?; let p2 = p3.next_table_create(addr.p3_index(), insert_flags)?; let p1 = p2.next_table_create(addr.p2_index(), insert_flags)?; let entry = &mut p1[addr.p1_index()]; if !entry.is_unused() { unsafe { self.unmap_single(page) }; // log::error!( // "Attempt to remap {:?} to {:?} with {:?}", // page, // frame, // flags // ); // log::error!( // "-- {:?} already mapped to {:?}, with {:?}", // page, // entry.frame(), // entry.flags() // ); // kbail!("Page already mapped to different frame"); } entry.set_frame(frame, flags); unsafe { tlb::flush(addr.value()) } Ok(()) } pub fn map_to( &mut self, pages: AllocatedPages, frames: AllocatedFrames, flags: PageTableFlags, ) -> KResult { assert_eq!( pages.size_in_pages(), frames.size_in_pages(), "Number of pages must equal number of frames" ); for (page, frame) in pages.iter().zip(frames.iter()) { self.map_to_single(page, frame, flags)?; } Ok(unsafe { MappedPages::assume_mapped(pages, frames, flags) }) } pub fn map(&mut self, pages: AllocatedPages, flags: PageTableFlags) -> KResult { let frames = alloc_kernel_frames(pages.size_in_pages())?; self.map_to(pages, frames, flags) } pub fn set_flags(&mut self, mp: &mut MappedPages, flags: PageTableFlags) { for page in mp.pages().iter() { let addr = page.start_address(); // these unwraps should be safe since we know the pages are already mapped let p3 = self.p4.next_table_mut(addr.p4_index()).unwrap(); let p2 = p3.next_table_mut(addr.p3_index()).unwrap(); let p1 = p2.next_table_mut(addr.p2_index()).unwrap(); p1[addr.p1_index()].set_flags(flags); unsafe { tlb::flush(addr.value()) }; } mp.flags = flags; } pub unsafe fn unmap(&mut self, mp: MappedPages) -> (AllocatedPages, AllocatedFrames) { for page in mp.pages().iter() { let addr = page.start_address(); // these unwraps should be safe since we know the pages are already mapped let p3 = self.p4.next_table_mut(addr.p4_index()).unwrap(); let p2 = p3.next_table_mut(addr.p3_index()).unwrap(); let p1 = p2.next_table_mut(addr.p2_index()).unwrap(); p1[addr.p1_index()].set_unused(); unsafe { tlb::flush(addr.value()) }; } let MappedPages { pages, frames, .. } = mp; (pages, frames) } pub unsafe fn unmap_single(&mut self, page: Page) -> Option { let addr = page.start_address(); // these unwraps should be safe since we know the pages are already mapped let p3 = self.p4.next_table_mut(addr.p4_index())?; let p2 = p3.next_table_mut(addr.p3_index())?; let p1 = p2.next_table_mut(addr.p2_index())?; let old_frame = p1[addr.p1_index()].frame(); p1[addr.p1_index()].set_unused(); unsafe { tlb::flush(addr.value()) }; old_frame } pub unsafe fn set_flags_single(&mut self, page: Page, flags: PageTableFlags) { let addr = page.start_address(); // these unwraps should be safe since we know the pages are already mapped let p3 = self.p4.next_table_mut(addr.p4_index()).unwrap(); let p2 = p3.next_table_mut(addr.p3_index()).unwrap(); let p1 = p2.next_table_mut(addr.p2_index()).unwrap(); p1[addr.p1_index()].set_flags(flags); unsafe { tlb::flush(addr.value()) }; } } ================================================ FILE: src/mem/paging/mod.rs ================================================ pub mod mapper; pub mod table; pub mod units; ================================================ FILE: src/mem/paging/table.rs ================================================ use core::{ fmt::Debug, ops::{Index, IndexMut}, }; use x86_64::{registers::control::Cr3, structures::paging::PageTableFlags}; use crate::{ kbail, mem::{ addr::{PhysAddr, VirtAddr}, allocator::alloc_kernel_frames, consts::{PAGE_SIZE, PAGE_TABLE_ENTRIES}, }, util::KResult, }; use super::units::{Frame, MemoryUnit}; fn frame_to_table(frame: Frame) -> *mut PageTable { let virt = crate::phys_offset() + frame.start_address().value(); virt.as_raw_ptr_mut() } #[derive(Clone, Copy)] #[repr(transparent)] pub struct PageTableEntry { data: usize, } impl PageTableEntry { const ADDRESS_MASK: usize = 0x000f_ffff_ffff_f000; const FLAGS_MASK: usize = 0x01ff; #[allow(clippy::new_without_default)] pub const fn new() -> Self { PageTableEntry { data: 0 } } pub const fn is_unused(&self) -> bool { self.data == 0 } pub fn set_unused(&mut self) { self.data = 0 } pub const fn flags(&self) -> PageTableFlags { PageTableFlags::from_bits_truncate(self.data as u64) } pub fn addr(&self) -> PhysAddr { PhysAddr::new(self.data & Self::ADDRESS_MASK) } pub fn frame(&self) -> Option { if !self.flags().contains(PageTableFlags::PRESENT) || self.flags().contains(PageTableFlags::HUGE_PAGE) { None } else { Some(Frame::containing_address(self.addr())) } } pub fn set_addr(&mut self, addr: PhysAddr, flags: PageTableFlags) { assert!(addr.is_aligned(PAGE_SIZE)); self.data = addr.value() | flags.bits() as usize; } pub fn set_frame(&mut self, frame: Frame, flags: PageTableFlags) { self.set_addr(frame.start_address(), flags) } pub fn set_flags(&mut self, flags: PageTableFlags) { self.data &= !Self::FLAGS_MASK; self.data |= flags.bits() as usize; } } impl Debug for PageTableEntry { fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { f.debug_struct("PageTableEntry") .field("addr", &self.addr()) .field("flags", &self.flags()) .finish() } } #[repr(C, align(4096))] #[derive(Clone)] pub struct PageTable { pub(super) entries: [PageTableEntry; PAGE_TABLE_ENTRIES], } impl PageTable { #[allow(clippy::new_without_default)] #[inline] pub const fn new() -> Self { Self { entries: [PageTableEntry::new(); PAGE_TABLE_ENTRIES], } } #[inline] pub fn zero(&mut self) { for entry in self.entries.iter_mut() { entry.set_unused(); } } pub fn next_table<'b>(&self, index: usize) -> Option<&'b PageTable> { let ptr = frame_to_table(self[index].frame()?); Some(unsafe { &*ptr }) } pub fn next_table_mut<'b>(&self, index: usize) -> Option<&'b mut PageTable> { let ptr = frame_to_table(self[index].frame()?); Some(unsafe { &mut *ptr }) } pub fn next_table_create<'b>( &mut self, index: usize, insert_flags: PageTableFlags, ) -> KResult<&'b mut PageTable> { let entry = &mut self[index]; let created; if entry.is_unused() { match alloc_kernel_frames(1) { Ok(frame) => { entry.set_frame(frame.start(), insert_flags); created = true; } Err(_e) => { kbail!("Could not allocate frame for new page table") } } } else { entry.set_flags(entry.flags() | insert_flags); created = false; } let page_table = match self.next_table_mut(index) { Some(pt) => pt, None => { kbail!("Could not get mutable reference to new page table, likely due to huge page") } }; if created { page_table.zero(); } Ok(page_table) } } impl Index for PageTable { type Output = PageTableEntry; fn index(&self, index: usize) -> &Self::Output { &self.entries[index] } } impl IndexMut for PageTable { fn index_mut(&mut self, index: usize) -> &mut Self::Output { &mut self.entries[index] } } impl Debug for PageTable { fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { writeln!( f, "PageTable[{:?}]", VirtAddr::new(self as *const _ as usize).as_hhdm_phys() )?; for (i, entry) in self.entries.iter().enumerate() { if !entry.is_unused() { writeln!(f, "{:>3}: {:>16?} | {:?}", i, entry.addr(), entry.flags())?; } } Ok(()) } } pub fn active_table() -> &'static mut PageTable { let cr3 = PhysAddr::new(Cr3::read().0.start_address().as_u64() as usize); unsafe { &mut *cr3.as_hhdm_virt().as_raw_ptr_mut() } } ================================================ FILE: src/mem/paging/units.rs ================================================ use core::{ fmt::Debug, ops::{Add, AddAssign, Sub, SubAssign}, }; use x86_64::structures::paging::PageTableFlags; use crate::mem::{ addr::{PhysAddr, VirtAddr}, consts::PAGE_SIZE, }; use super::mapper::Mapper; #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] #[repr(transparent)] pub struct PageIndex(pub usize); impl PageIndex { #[inline] pub fn new(index: usize) -> Self { Self(index) } #[inline] pub fn containing_physaddr(paddr: PhysAddr) -> Self { Self::new(paddr.value() / PAGE_SIZE) } #[inline] pub fn containing_virtaddr(vaddr: VirtAddr) -> Self { Self::new(vaddr.value() / PAGE_SIZE) } #[inline] pub fn as_physaddr(self) -> PhysAddr { PhysAddr::new(self.0 * PAGE_SIZE) } #[inline] pub fn as_virtaddr(self) -> VirtAddr { VirtAddr::new(self.0 * PAGE_SIZE) } } impl Add for PageIndex { type Output = Self; fn add(self, rhs: usize) -> Self::Output { Self(self.0 + rhs) } } impl Sub for PageIndex { type Output = Self; fn sub(self, rhs: usize) -> Self::Output { Self(self.0 - rhs) } } impl AddAssign for PageIndex { fn add_assign(&mut self, rhs: usize) { self.0 += rhs } } impl SubAssign for PageIndex { fn sub_assign(&mut self, rhs: usize) { self.0 -= rhs } } pub trait MemoryUnit: Copy + Debug + PartialEq + Eq + PartialOrd + Ord + Add + Sub { type Address: Copy + Debug + PartialEq + Eq + PartialOrd + Ord + Add + Sub + core::fmt::Pointer + core::fmt::LowerHex; fn containing_address(addr: Self::Address) -> Self; fn at_index(index: PageIndex) -> Self; fn start_address(self) -> Self::Address; fn inclusive_end_address(self) -> Self::Address; fn index(self) -> PageIndex; } #[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] #[repr(transparent)] pub struct Frame { index: PageIndex, } impl MemoryUnit for Frame { type Address = PhysAddr; #[inline] fn containing_address(addr: PhysAddr) -> Self { Self { index: PageIndex::containing_physaddr(addr), } } #[inline] fn at_index(index: PageIndex) -> Self { Self { index } } #[inline] fn start_address(self) -> Self::Address { self.index.as_physaddr() } #[inline] fn inclusive_end_address(self) -> Self::Address { self.start_address() + PAGE_SIZE - 1 } #[inline] fn index(self) -> PageIndex { self.index } } impl Debug for Frame { fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { write!(f, "Frame<{:x}>", self.start_address()) } } impl Add for Frame { type Output = Self; fn add(self, rhs: usize) -> Self::Output { Self::at_index(self.index + rhs) } } impl Sub for Frame { type Output = Self; fn sub(self, rhs: usize) -> Self::Output { Self::at_index(self.index - rhs) } } impl AddAssign for Frame { fn add_assign(&mut self, rhs: usize) { self.index += rhs } } impl SubAssign for Frame { fn sub_assign(&mut self, rhs: usize) { self.index -= rhs } } #[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] pub struct Page { index: PageIndex, } impl MemoryUnit for Page { type Address = VirtAddr; #[inline] fn containing_address(addr: VirtAddr) -> Self { Self { index: PageIndex::containing_virtaddr(addr), } } #[inline] fn at_index(index: PageIndex) -> Self { Self { index } } #[inline] fn start_address(self) -> Self::Address { self.index.as_virtaddr() } #[inline] fn inclusive_end_address(self) -> Self::Address { self.index.as_virtaddr() + PAGE_SIZE - 1 } #[inline] fn index(self) -> PageIndex { self.index } } impl Debug for Page { fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { write!(f, "Page<{:x}>", self.start_address()) } } impl Add for Page { type Output = Self; fn add(self, rhs: usize) -> Self::Output { Self::at_index(self.index + rhs) } } impl Sub for Page { type Output = Self; fn sub(self, rhs: usize) -> Self::Output { Self::at_index(self.index - rhs) } } impl AddAssign for Page { fn add_assign(&mut self, rhs: usize) { self.index += rhs } } impl SubAssign for Page { fn sub_assign(&mut self, rhs: usize) { self.index -= rhs } } #[derive(Debug)] pub struct PageMergeError; #[derive(Debug, Clone, Copy)] pub struct MemoryRange { pub start: T, pub end: T, } impl MemoryRange { #[inline] pub fn new(start: T, end: T) -> Self { Self { start, end } } #[inline] pub fn empty() -> Self { Self { start: T::at_index(PageIndex(0)), end: T::at_index(PageIndex(0)), } } #[inline] pub fn start(self) -> T { self.start } #[inline] pub fn end(self) -> T { self.end } #[inline] pub fn size_in_pages(self) -> usize { if self.is_empty() { return 0; } self.end.index().0 - self.start.index().0 } #[inline] pub fn size_in_bytes(self) -> usize { self.size_in_pages() * PAGE_SIZE } #[inline] pub fn is_empty(self) -> bool { self.start >= self.end } #[inline] pub fn start_address(self) -> T::Address { self.start.start_address() } #[inline] pub fn end_address(self) -> T::Address { self.end.start_address() } #[inline] pub fn inclusive_end_address(self) -> T::Address { self.end_address() - 1 } pub fn merge_with(&mut self, other: Self) -> Result<(), PageMergeError> { if other.is_empty() { return Ok(()); } if other.start != self.end && other.end != self.start { return Err(PageMergeError); } if other.start < self.start { self.start = other.start; } if other.end > self.end { self.end = other.end; } Ok(()) } pub fn overlaps(self, other: Self) -> bool { if self.is_empty() || other.is_empty() { return false; } self.start <= other.end && other.start <= self.end } pub fn consumes(self, other: Self) -> bool { if self.is_empty() || other.is_empty() { return false; } self.start <= other.start && self.end >= other.end } pub fn contains(self, unit: T) -> bool { if self.is_empty() { return false; } self.start <= unit && self.end >= unit } pub fn iter(self) -> MemoryRangeIter { MemoryRangeIter { current: self.start, limit: self.end, } } } pub type FrameRange = MemoryRange; pub type PageRange = MemoryRange; pub struct MemoryRangeIter { current: T, limit: T, } impl Iterator for MemoryRangeIter { type Item = T; fn next(&mut self) -> Option { if self.current >= self.limit { None } else { let current = self.current; self.current = self.current + 1; Some(current) } } } pub struct Allocated { pub range: MemoryRange, } impl Allocated { pub unsafe fn assume_allocated(range: MemoryRange) -> Self { Self { range } } } impl Debug for Allocated { fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { write!( f, "Allocated[{:x} .. {:x}]", self.range.start_address(), self.range.inclusive_end_address() ) } } pub type AllocatedFrames = Allocated; pub type AllocatedPages = Allocated; impl core::ops::Deref for Allocated { type Target = MemoryRange; fn deref(&self) -> &Self::Target { &self.range } } impl core::ops::DerefMut for Allocated { fn deref_mut(&mut self) -> &mut Self::Target { &mut self.range } } pub struct MappedPages { pub(super) pages: AllocatedPages, pub(super) frames: AllocatedFrames, pub(super) flags: PageTableFlags, } impl MappedPages { pub unsafe fn assume_mapped( pages: AllocatedPages, frames: AllocatedFrames, flags: PageTableFlags, ) -> Self { Self { pages, frames, flags, } } pub unsafe fn unmap(self, table: &mut Mapper) -> (AllocatedPages, AllocatedFrames) { unsafe { table.unmap(self) } } pub fn pages(&self) -> &AllocatedPages { &self.pages } pub fn frames(&self) -> &AllocatedFrames { &self.frames } pub fn flags(&self) -> PageTableFlags { self.flags } } impl Debug for MappedPages { fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { f.debug_struct("MappedPages") .field("pages", &self.pages) .field("frames", &self.frames) .finish() } } ================================================ FILE: src/serial.rs ================================================ use lazy_static::lazy_static; use uart_16550::SerialPort; use x86::io::inb; use crate::util::lock::IrqMutex; pub const SERIAL0_IOPORT: u16 = 0x3f8; pub const SERIAL1_IOPORT: u16 = 0x2f8; pub const SERIAL2_IOPORT: u16 = 0x3e8; lazy_static! { pub static ref SERIAL0: IrqMutex = { let mut serial_port = unsafe { SerialPort::new(SERIAL0_IOPORT) }; serial_port.init(); IrqMutex::new(serial_port) }; pub static ref SERIAL1: IrqMutex = { let mut serial_port = unsafe { SerialPort::new(SERIAL1_IOPORT) }; serial_port.init(); IrqMutex::new(serial_port) }; pub static ref SERIAL2: IrqMutex = { let mut serial_port = unsafe { SerialPort::new(SERIAL2_IOPORT) }; serial_port.init(); IrqMutex::new(serial_port) }; } #[doc(hidden)] #[allow(unreachable_code)] pub fn _print0(args: ::core::fmt::Arguments) { use core::fmt::Write; // #[cfg(debug_assertions)] x86_64::instructions::interrupts::without_interrupts(|| { SERIAL0 .lock() .write_fmt(args) .expect("Printing to serial failed"); }); } /// Prints to the host through the logging serial interface. #[macro_export] macro_rules! serial0_print { ($($arg:tt)*) => { $crate::serial::_print0(format_args!($($arg)*)) }; } /// Prints to the host through the logging serial interface, appending a newline. #[macro_export] macro_rules! serial0_println { () => ($crate::serial0_print!("\n")); ($fmt:expr) => ($crate::serial0_print!(concat!($fmt, "\n"))); ($fmt:expr, $($arg:tt)*) => ($crate::serial0_print!( concat!($fmt, "\n"), $($arg)*)); } #[doc(hidden)] #[allow(unreachable_code)] pub fn _print1(args: ::core::fmt::Arguments) { use core::fmt::Write; // #[cfg(debug_assertions)] x86_64::instructions::interrupts::without_interrupts(|| { SERIAL1 .lock() .write_fmt(args) .expect("Printing to serial failed"); }); } #[inline] pub fn serial1_recv() -> Option { // #[cfg(debug_assertions)] unsafe { let line_sts = inb(SERIAL1_IOPORT + 5); if line_sts & 0x1 != 0 { return Some(inb(SERIAL1_IOPORT)); } None } // #[cfg(not(debug_assertions))] // None // }) } /// Prints to the host through the user serial interface. #[macro_export] macro_rules! serial1_print { ($($arg:tt)*) => { $crate::serial::_print1(format_args!($($arg)*)) }; } /// Prints to the host through the user serial interface, appending a newline. #[macro_export] macro_rules! serial1_println { () => ($crate::serial1_print!("\r\n")); ($fmt:expr) => ($crate::serial1_print!(concat!($fmt, "\r\n"))); ($fmt:expr, $($arg:tt)*) => ($crate::serial1_print!( concat!($fmt, "\r\n"), $($arg)*)); } #[doc(hidden)] #[allow(unreachable_code)] pub fn _print2(args: ::core::fmt::Arguments) { use core::fmt::Write; // #[cfg(debug_assertions)] x86_64::instructions::interrupts::without_interrupts(|| { SERIAL2 .lock() .write_fmt(args) .expect("Printing to serial failed"); }); } /// Prints to the host through the TTY logging serial interface. #[macro_export] macro_rules! serial2_print { ($($arg:tt)*) => { $crate::serial::_print2(format_args!($($arg)*)) }; } /// Prints to the host through the TTY logging serial interface, appending a newline. #[macro_export] macro_rules! serial2_println { () => ($crate::serial2_print!("\n")); ($fmt:expr) => ($crate::serial2_print!(concat!($fmt, "\n"))); ($fmt:expr, $($arg:tt)*) => ($crate::serial2_print!( concat!($fmt, "\n"), $($arg)*)); } ================================================ FILE: src/task/group.rs ================================================ use alloc::{ sync::{Arc, Weak}, vec::Vec, }; use crate::util::IrqMutex; use super::{get_scheduler, signal::Signal, Task}; pub type PgId = i32; pub struct TaskGroup { pgid: PgId, tasks: Vec>, } impl TaskGroup { pub(super) fn new(pgid: PgId) -> Arc> { Arc::new(IrqMutex::new(TaskGroup { pgid, tasks: Vec::new(), })) } pub fn pgid(&self) -> PgId { self.pgid } pub fn set_pgid(&mut self, pgid: PgId) { self.pgid = pgid } pub fn add(&mut self, task: Weak) { self.tasks.push(task) } pub fn remove(&mut self, task: &Weak) { self.tasks.retain(|p| !Weak::ptr_eq(p, task)); if self.tasks.is_empty() { get_scheduler().task_groups.lock().remove(&self.pgid); } } pub fn gc_dropped_processes(&mut self) { self.tasks.retain(|task| task.upgrade().is_some()); if self.tasks.is_empty() { get_scheduler().task_groups.lock().remove(&self.pgid); } } pub fn signal(&mut self, signal: Signal) { for task in self.tasks.iter() { get_scheduler().send_signal_to(task.upgrade().unwrap(), signal); } } } ================================================ FILE: src/task/mod.rs ================================================ use core::{ cell::UnsafeCell, sync::atomic::{AtomicUsize, Ordering}, }; use alloc::{ sync::{Arc, Weak}, vec::Vec, }; use atomic_refcell::AtomicRefCell; use crossbeam_utils::atomic::AtomicCell; use spin::Once; use x86_64::structures::idt::PageFaultErrorCode; use crate::{ arch::{ idt::{InterruptErrorFrame, InterruptFrame}, task::ArchTask, }, fs::{ initramfs::{get_root, root::RootFs}, opened_file::{FileDesc, LocalOpenedFile, OpenedFileTable}, FileRef, }, mem::addr::VirtAddr, util::{ctypes::c_int, IrqMutex, KResult}, }; use self::{ group::{PgId, TaskGroup}, scheduler::Scheduler, signal::{SigSet, SignalDelivery, SignalMask}, vmem::Vmem, wait_queue::WaitQueue, }; pub mod group; pub mod scheduler; pub mod signal; pub mod vmem; pub mod wait_queue; pub static SCHEDULER: Once> = Once::new(); pub static JOIN_WAIT_QUEUE: WaitQueue = WaitQueue::new(); pub fn init() { SCHEDULER.call_once(Scheduler::new); } pub fn get_scheduler() -> &'static Arc { SCHEDULER.get().unwrap() } pub fn current_task() -> Arc { get_scheduler().current_task() } #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] pub struct TaskId(usize); impl TaskId { pub const fn new(pid: usize) -> Self { Self(pid) } fn allocate() -> Self { static NEXT_PID: AtomicUsize = AtomicUsize::new(1); Self::new(NEXT_PID.fetch_add(1, Ordering::AcqRel)) } pub fn as_usize(&self) -> usize { self.0 } } #[derive(Debug, Clone, Copy, PartialEq)] pub enum TaskState { Runnable, Waiting, ExitedWith(c_int), } pub struct Task { sref: Weak, arch: UnsafeCell, state: AtomicCell, pid: TaskId, pub(crate) start_time: Once, pub(crate) root_fs: Arc>, pub(crate) opened_files: Arc>, parent: IrqMutex>, pub(crate) children: Arc>>>, pub(crate) group: AtomicRefCell>>, vmem: Arc>, pub(crate) signals: Arc>, signaled_frame: AtomicCell>, sigset: Arc>, } unsafe impl Sync for Task {} impl Task { pub fn new_idle(sched: &mut Scheduler) -> Arc { let pid = TaskId::new(0); let group = sched.find_or_create_group(0); let t = Arc::new_cyclic(|sref| Self { sref: sref.clone(), arch: UnsafeCell::new(ArchTask::new_idle()), state: AtomicCell::new(TaskState::Runnable), pid, start_time: Once::new(), parent: IrqMutex::new(Weak::new()), children: Arc::new(IrqMutex::new(Vec::new())), root_fs: Arc::new(IrqMutex::new(get_root().unwrap().clone())), opened_files: Arc::new(IrqMutex::new(OpenedFileTable::new())), vmem: Arc::new(IrqMutex::new(Vmem::new())), signaled_frame: AtomicCell::new(None), signals: Arc::new(IrqMutex::new(SignalDelivery::new())), sigset: Arc::new(IrqMutex::new(SigSet::ZERO)), group: AtomicRefCell::new(Arc::downgrade(&group)), }); group.lock().add(Arc::downgrade(&t)); t } pub fn new_kernel(sched: &Scheduler, entry_point: fn(), enable_interrupts: bool) -> Arc { let pid = TaskId::allocate(); let group = sched.find_or_create_group(0); let t = Arc::new_cyclic(|sref| Self { sref: sref.clone(), arch: UnsafeCell::new(ArchTask::new_kernel( VirtAddr::new(entry_point as usize), enable_interrupts, )), group: AtomicRefCell::new(Arc::downgrade(&group)), state: AtomicCell::new(TaskState::Runnable), pid, start_time: Once::new(), parent: IrqMutex::new(Weak::new()), children: Arc::new(IrqMutex::new(Vec::new())), root_fs: Arc::new(IrqMutex::new(get_root().unwrap().clone())), opened_files: Arc::new(IrqMutex::new(OpenedFileTable::new())), vmem: Arc::new(IrqMutex::new(Vmem::new())), signaled_frame: AtomicCell::new(None), signals: Arc::new(IrqMutex::new(SignalDelivery::new())), sigset: Arc::new(IrqMutex::new(SigSet::ZERO)), }); group.lock().add(Arc::downgrade(&t)); t } pub fn exec(&self, file: FileRef, argv: &[&[u8]], envp: &[&[u8]]) -> KResult<()> { { self.opened_files.lock().close_cloexec_files(); self.arch_mut().address_space.with_mapper(|mut mapper| { self.vmem.lock().clear(&mut mapper); }); *self.signals.lock() = SignalDelivery::new(); *self.sigset.lock() = SigSet::ZERO; self.signaled_frame.store(None); } let lock = &mut self.vmem.lock(); unsafe { self.vmem.force_unlock() }; self.arch_mut().exec(lock, file, argv, envp) } pub fn make_child(&self, arch: UnsafeCell) -> Arc { let pid = TaskId::allocate(); let group = self.group.borrow().upgrade().unwrap(); let new = Arc::new_cyclic(|sref| Self { sref: sref.clone(), arch, root_fs: Arc::new(IrqMutex::new(self.root_fs.lock().clone())), opened_files: Arc::new(IrqMutex::new(self.opened_files.lock().clone())), // todo: deeper clone children: Arc::new(IrqMutex::new(Vec::new())), parent: IrqMutex::new(Weak::new()), group: AtomicRefCell::new(Arc::downgrade(&group)), state: AtomicCell::new(TaskState::Runnable), pid, start_time: Once::new(), vmem: Arc::new(IrqMutex::new(Vmem::new())), signals: Arc::new(IrqMutex::new(SignalDelivery::new())), signaled_frame: AtomicCell::new(None), sigset: Arc::new(IrqMutex::new(SigSet::ZERO)), }); self.add_child(new.clone()); new.signals.lock().clone_from(&self.signals.lock()); new.vmem.lock().fork_from(&self.vmem.lock()); group.lock().add(Arc::downgrade(&new)); get_scheduler().push_runnable(new.clone(), false); new } pub fn fork(&self) -> Arc { let arch = UnsafeCell::new(self.arch_mut().fork().unwrap()); self.make_child(arch) } pub fn clone_process( &self, entry_point: VirtAddr, user_stack: VirtAddr, args: VirtAddr, r8: usize, r9: usize, syscall_frame: &InterruptFrame, ) -> Arc { let arch = UnsafeCell::new( self.arch_mut() .clone_process(entry_point, user_stack, args, r8, r9, syscall_frame) .unwrap(), ); let pid = TaskId::allocate(); let group = self.group.borrow().upgrade().unwrap(); let t = Arc::new_cyclic(|sref| Self { sref: sref.clone(), arch, opened_files: Arc::new(IrqMutex::new(self.opened_files.lock().clone())), // todo: deeper clone state: AtomicCell::new(TaskState::Runnable), pid, start_time: Once::new(), root_fs: Arc::new(IrqMutex::new(self.root_fs.lock().clone())), // todo: actually fork the root fs children: Arc::new(IrqMutex::new(Vec::new())), parent: IrqMutex::new(Weak::new()), group: AtomicRefCell::new(Arc::downgrade(&group)), signals: Arc::new(IrqMutex::new(self.signals.lock().clone())), signaled_frame: AtomicCell::new(None), sigset: Arc::new(IrqMutex::new(SigSet::ZERO)), vmem: self.vmem.clone(), // important: we don't fork_from here }); self.add_child(t.clone()); group.lock().add(Arc::downgrade(&t)); get_scheduler().push_runnable(t.clone(), false); t } fn add_child(&self, child: Arc) { let mut children = self.children.lock(); child.set_parent(self.sref.clone()); children.push(child); } #[allow(clippy::mut_from_ref)] // FIXME pub fn arch_mut(&self) -> &mut ArchTask { unsafe { &mut *self.arch.get() } } pub fn pid(&self) -> TaskId { self.pid } pub fn ppid(&self) -> TaskId { if let Some(parent) = self.parent.lock().upgrade() { parent.pid } else { TaskId::new(0) } } pub fn pgid(&self) -> Option { Some(self.group.borrow().upgrade()?.lock().pgid()) } pub fn get_state(&self) -> TaskState { self.state.load() } pub fn set_state(&self, state: TaskState) { if !matches!(self.get_state(), TaskState::ExitedWith(_)) { self.state.store(state) } else { unreachable!(); } } fn set_parent(&self, parent: Weak) { *self.parent.lock() = parent; } pub fn belongs_to_group(&self, pg: &Weak>) -> bool { Weak::ptr_eq(&self.group.borrow(), pg) } pub fn get_opened_file_by_fd(&self, fd: FileDesc) -> KResult { Ok(self.opened_files.lock().get(fd)?.clone()) } pub fn vmem(&self) -> Arc> { self.vmem.clone() } pub fn handle_page_fault( &self, faulted_addr: VirtAddr, stack_frame: InterruptErrorFrame, reason: PageFaultErrorCode, ) -> KResult<()> { let addr_space = &mut self.arch_mut().address_space; self.vmem .try_lock()? .handle_page_fault(addr_space, faulted_addr, stack_frame, reason) } pub fn set_signal_mask( &self, how: SignalMask, set: VirtAddr, oldset: &mut VirtAddr, _length: usize, ) -> KResult<()> { let mut sigset = self.sigset.lock(); if !oldset.is_null() { let slice = sigset.as_raw_slice(); assert_eq!(slice.len(), 8); unsafe { oldset.write_bytes_user(slice) }?; } if !set.is_null() { let new_set = unsafe { set.read_user::<[u8; 8]>()? }; let new_set = SigSet::new(new_set); match how { SignalMask::Block => *sigset |= new_set, SignalMask::Unblock => *sigset &= !new_set, SignalMask::Set => *sigset = new_set, } } Ok(()) } pub fn has_pending_signals(&self) -> bool { self.signals.lock().is_pending() } } ================================================ FILE: src/task/scheduler.rs ================================================ use alloc::{ collections::{BTreeMap, VecDeque}, sync::Arc, vec::Vec, }; use spin::RwLock; use x86_64::instructions::interrupts::enable_and_hlt; use crate::{ arch::{ self, idt::InterruptFrame, startup_init, task::{arch_context_switch, ArchTask}, time, }, fs::POLL_WAIT_QUEUE, kerror, mem::addr::VirtAddr, task::JOIN_WAIT_QUEUE, util::{ctypes::c_int, IrqMutex, KResult}, }; use super::{ get_scheduler, group::{PgId, TaskGroup}, signal::{SigAction, Signal, SIGCHLD}, wait_queue::WaitQueue, Task, TaskId, TaskState, }; pub struct Scheduler { tasks: Arc>>>, run_queue: Arc>>>, waiting_queue: Arc>>>, #[allow(clippy::type_complexity)] deadline_waiting_queue: Arc, usize)>>>, idle_thread: Option>, preempt_task: Option>, current_task: Arc>>>, exited_tasks: Arc>>>, pub(super) task_groups: IrqMutex>>>, } impl Scheduler { pub fn new() -> Arc { let mut s = Self { tasks: Arc::new(IrqMutex::new(BTreeMap::new())), run_queue: Arc::new(IrqMutex::new(VecDeque::new())), waiting_queue: Arc::new(IrqMutex::new(VecDeque::new())), deadline_waiting_queue: Arc::new(IrqMutex::new(VecDeque::new())), idle_thread: None, preempt_task: None, current_task: Arc::new(RwLock::new(None)), task_groups: IrqMutex::new(BTreeMap::new()), exited_tasks: Arc::new(IrqMutex::new(Vec::new())), }; let idle_thread = Task::new_idle(&mut s); let init_task = Task::new_kernel(&s, startup_init, false); s.push_runnable(init_task, false); let preempt_task = Task::new_kernel(&s, preempt, false); let reaper_task = Task::new_kernel(&s, reap, true); s.idle_thread = Some(idle_thread); s.preempt_task = Some(preempt_task); s.push_runnable(reaper_task, false); Arc::new(s) } pub fn push_runnable(&self, task: Arc, priority: bool) { task.state.store(TaskState::Runnable); let mut queue = self.run_queue.lock(); self.tasks.lock().try_insert(task.pid, task.clone()).ok(); self.waiting_queue.lock().retain(|t| t.pid != task.pid); let already_in_queue = queue.iter().any(|t| t.pid == task.pid); if !already_in_queue { if priority { queue.push_front(task); } else { queue.push_back(task); } } } pub fn push_waiting(&self, task: Arc) { task.state.store(TaskState::Waiting); let mut queue = self.waiting_queue.lock(); self.tasks.lock().try_insert(task.pid, task.clone()).ok(); self.run_queue.lock().retain(|t| t.pid != task.pid); self.deadline_waiting_queue .lock() .retain(|(t, _)| t.pid != task.pid); let already_in_queue = queue.iter().any(|t| t.pid == task.pid); if !already_in_queue { queue.push_back(task); } } pub fn push_deadline_waiting(&self, task: Arc, duration: usize) { task.state.store(TaskState::Waiting); let mut queue = self.deadline_waiting_queue.lock(); self.tasks.lock().try_insert(task.pid, task.clone()).ok(); self.run_queue.lock().retain(|t| t.pid != task.pid); self.waiting_queue.lock().retain(|t| t.pid != task.pid); let already_in_queue = queue.iter().any(|(t, _)| t.pid == task.pid); if !already_in_queue { let deadline = arch::time::get_uptime_ms() + duration; queue.push_back((task, deadline)); } } fn check_deadline(&self) { let time = arch::time::get_uptime_ms(); let mut queue = self.deadline_waiting_queue.lock(); for _ in 0..queue.len() { if let Some((task, deadline)) = queue.pop_front() { if deadline <= time { // time's up! drop(queue); self.push_runnable(task, true); queue = self.deadline_waiting_queue.lock(); } else { queue.push_back((task, deadline)); } } } } pub fn current_task_opt(&self) -> Option> { let current = self.current_task.try_read()?; let clone = current.as_ref().cloned(); drop(current); clone } pub fn current_task(&self) -> Arc { let current = self.current_task.read(); let clone = current.as_ref().unwrap().clone(); drop(current); clone } pub fn find_task(&self, pid: TaskId) -> Option> { self.tasks.lock().get(&pid).cloned() } pub fn find_group(&self, pgid: PgId) -> Option>> { self.task_groups.lock().get(&pgid).cloned() } pub fn find_or_create_group(&self, pgid: PgId) -> Arc> { self.find_group(pgid).unwrap_or_else(|| { let g = TaskGroup::new(pgid); self.task_groups.lock().insert(pgid, g.clone()); g }) } pub fn exit_current(&self, status: c_int) { let current = self.current_task(); if current.pid.as_usize() == 1 { panic!("init (pid=1) tried to exit with status {}", status); } current.set_state(TaskState::ExitedWith(status)); if let Some(parent) = current.parent.lock().upgrade() { let mut parent_signals = parent.signals.lock(); if parent_signals.get_action(SIGCHLD) == SigAction::Ignore { parent.children.lock().retain(|p| p.pid != current.pid); } else { log::debug!("Sending SIGCHLD to {}", parent.pid.as_usize()); parent_signals.signal(SIGCHLD); } } current.opened_files.lock().close_all(); self.run_queue.lock().retain(|t| t.pid != current.pid); self.waiting_queue.lock().retain(|t| t.pid != current.pid); self.deadline_waiting_queue .lock() .retain(|t| t.0.pid != current.pid); self.tasks.lock().remove(¤t.pid); self.exited_tasks.lock().push(current); self.wake_all(&JOIN_WAIT_QUEUE); self.preempt(); } pub fn wake_all(&self, queue: &WaitQueue) { let mut q = queue.queue.lock(); while let Some(proc) = q.pop_front() { self.resume_task(proc) } } pub fn send_signal_to(&self, task: Arc, signal: Signal) { task.signals.lock().signal(signal); self.resume_task(task); } pub fn resume_task(&self, task: Arc) { self.push_runnable(task, false); } pub fn try_delivering_signal( &self, frame: &mut InterruptFrame, syscall_result: isize, ) -> KResult<()> { let current = self.current_task(); if let Some((signal, sigaction)) = current.signals.lock().pop_pending() { let mut set = current.sigset.lock(); if !set.get(signal as usize).as_deref().unwrap_or(&true) { match sigaction { SigAction::Ignore => {} SigAction::Terminate => { log::trace!( "terminating pid {} by signal {:?}", current.pid.as_usize(), signal ); self.exit_current(1); } SigAction::Handler { handler } => { log::trace!( "delivering signal {:?} to pid {} (handler addr {:#x})", signal, current.pid.as_usize(), handler as usize ); current.signaled_frame.store(Some(*frame)); set.set(signal as usize, true); ArchTask::setup_signal_stack( frame, signal, VirtAddr::new(handler as usize), syscall_result, )?; } } } } Ok(()) } pub fn restore_signaled_user_stack(&self, current_frame: &mut InterruptFrame) { let current = self.current_task(); if let Some(signaled_frame) = current.signaled_frame.swap(None) { *current_frame = signaled_frame; } else { log::warn!("User called sigreturn(2) while it is not signaled"); } } pub fn reap_dead(&self) { let mut exited = self.exited_tasks.lock(); for task in exited.iter() { self.tasks.lock().remove(&task.pid); self.run_queue.lock().retain(|t| t.pid != task.pid); self.waiting_queue.lock().retain(|t| t.pid != task.pid); JOIN_WAIT_QUEUE.queue.lock().retain(|t| t.pid != task.pid); POLL_WAIT_QUEUE.queue.lock().retain(|t| t.pid != task.pid); if let Some(group) = task.group.borrow_mut().upgrade() { group.lock().gc_dropped_processes(); } // assert_eq!(Arc::strong_count(task), 1, "PID {} has dangling references", task.pid.as_usize()); } exited.clear(); } pub fn preempt(&self) { let current = self.current_task.read(); if let Some(current_task) = current.as_ref().cloned() { // log::debug!("Switching from PID {:?} to preempt task", current_task.pid); drop(current); arch_context_switch( current_task.arch_mut(), self.preempt_task.as_ref().unwrap().arch_mut(), ); } else { // log::debug!("Switching from idle thread to preempt task"); drop(current); arch_context_switch( self.idle_thread.as_ref().unwrap().arch_mut(), self.preempt_task.as_ref().unwrap().arch_mut(), ); } } pub fn sleep(&self, duration: Option) -> KResult<()> { let task = self.current_task(); if let Some(duration) = duration { self.push_deadline_waiting(task.clone(), duration); } else { self.push_waiting(task.clone()); } self.preempt(); if task.has_pending_signals() { Err(kerror!(EINTR, "sleep(): pending signals")) } else { Ok(()) } } } pub fn switch() { let sched = get_scheduler(); sched.check_deadline(); let mut queue = sched.run_queue.lock(); let current = sched.current_task.try_write(); if current.is_none() { log::warn!("Couldn't lock current task for writing."); return; } let mut current = current.unwrap(); let mut task = None; loop { let t = queue.pop_front(); if let Some(t) = t { if t.get_state() == TaskState::Runnable { task = Some(t); break; } else { // we'll take the opportunity to purge the run queue of sleeping/dead tasks } } else { break; } } if let Some(task) = task { if let Some(current_task) = current.as_ref() { if current_task.pid != task.pid { queue.push_back(current_task.clone()); } } *current = Some(task.clone()); drop(queue); drop(current); task.start_time.call_once(time::get_uptime_ms); // log::debug!("Switching from preempt task to PID {}", task.pid.as_usize()); arch_context_switch( sched.preempt_task.as_ref().unwrap().arch_mut(), task.arch_mut(), ); } else { if let Some(current_task) = current.as_ref().cloned() { let state = { current_task.get_state() }; if state == TaskState::Runnable { drop(current); drop(queue); // log::debug!( // "Switching from preempt task to PID {}", // current_task.pid.as_usize() // ); arch_context_switch( sched.preempt_task.as_ref().unwrap().arch_mut(), current_task.arch_mut(), ); return; } } *current = None; drop(current); drop(queue); // log::debug!("Switching from preempt task to idle thread"); arch_context_switch( sched.preempt_task.as_ref().unwrap().arch_mut(), sched.idle_thread.as_ref().unwrap().arch_mut(), ); } } fn reap() { let scheduler = get_scheduler(); loop { scheduler.reap_dead(); enable_and_hlt(); } } fn preempt() { loop { switch(); } } ================================================ FILE: src/task/signal.rs ================================================ use bitvec::prelude::*; use crate::{ kbail, util::{ctypes::c_int, error::KResult}, }; pub type Signal = c_int; #[allow(unused)] pub const SIGHUP: Signal = 1; #[allow(unused)] pub const SIGINT: Signal = 2; #[allow(unused)] pub const SIGQUIT: Signal = 3; #[allow(unused)] pub const SIGILL: Signal = 4; #[allow(unused)] pub const SIGTRAP: Signal = 5; #[allow(unused)] pub const SIGABRT: Signal = 6; #[allow(unused)] pub const SIGBUS: Signal = 7; #[allow(unused)] pub const SIGFPE: Signal = 8; #[allow(unused)] pub const SIGKILL: Signal = 9; #[allow(unused)] pub const SIGUSR1: Signal = 10; #[allow(unused)] pub const SIGSEGV: Signal = 11; #[allow(unused)] pub const SIGUSR2: Signal = 12; #[allow(unused)] pub const SIGPIPE: Signal = 13; #[allow(unused)] pub const SIGALRM: Signal = 14; #[allow(unused)] pub const SIGTERM: Signal = 15; #[allow(unused)] pub const SIGSTKFLT: Signal = 16; #[allow(unused)] pub const SIGCHLD: Signal = 17; #[allow(unused)] pub const SIGCONT: Signal = 18; #[allow(unused)] pub const SIGSTOP: Signal = 19; #[allow(unused)] pub const SIGTSTP: Signal = 20; #[allow(unused)] pub const SIGTTIN: Signal = 21; #[allow(unused)] pub const SIGTTOU: Signal = 22; #[allow(unused)] pub const SIGURG: Signal = 23; #[allow(unused)] pub const SIGXCPU: Signal = 24; #[allow(unused)] pub const SIGXFSZ: Signal = 25; #[allow(unused)] pub const SIGVTALRM: Signal = 26; #[allow(unused)] pub const SIGPROF: Signal = 27; #[allow(unused)] pub const SIGWINCH: Signal = 28; #[allow(unused)] pub const SIGIO: Signal = 29; #[allow(unused)] pub const SIGPWR: Signal = 30; #[allow(unused)] pub const SIGSYS: Signal = 31; const SIGMAX: c_int = 32; pub const SIG_DFL: usize = 0; pub const SIG_IGN: usize = 1; pub const SIG_ERR: usize = usize::MAX; #[derive(Clone, Copy, PartialEq, Eq)] pub enum SigAction { Ignore, Terminate, Handler { handler: fn() }, } pub const DEFAULT_ACTIONS: [SigAction; SIGMAX as usize] = [ /* (unused) */ SigAction::Ignore, /* SIGHUP */ SigAction::Terminate, /* SIGINT */ SigAction::Terminate, /* SIGQUIT */ SigAction::Terminate, /* SIGILL */ SigAction::Terminate, /* SIGTRAP */ SigAction::Ignore, /* SIGABRT */ SigAction::Terminate, /* SIGBUS */ SigAction::Terminate, /* SIGFPE */ SigAction::Terminate, /* SIGKILL */ SigAction::Terminate, /* SIGUSR1 */ SigAction::Ignore, /* SIGSEGV */ SigAction::Terminate, /* SIGUSR2 */ SigAction::Ignore, /* SIGPIPE */ SigAction::Ignore, /* SIGALRM */ SigAction::Ignore, /* SIGTERM */ SigAction::Terminate, /* SIGSTKFLT */ SigAction::Ignore, /* SIGCHLD */ SigAction::Ignore, /* SIGCONT */ SigAction::Terminate, /* SIGSTOP */ SigAction::Ignore, /* SIGTSTP */ SigAction::Ignore, /* SIGTTIN */ SigAction::Ignore, /* SIGTTOU */ SigAction::Ignore, /* SIGURG */ SigAction::Ignore, /* SIGXCPU */ SigAction::Ignore, /* SIGXFSZ */ SigAction::Ignore, /* SIGVTALRM */ SigAction::Ignore, /* SIGPROF */ SigAction::Ignore, /* SIGWINCH */ SigAction::Ignore, /* SIGIO */ SigAction::Ignore, /* SIGPWR */ SigAction::Ignore, /* SIGSYS */ SigAction::Ignore, ]; #[derive(Clone)] pub struct SignalDelivery { pending: u32, actions: [SigAction; SIGMAX as usize], } impl Default for SignalDelivery { fn default() -> Self { Self::new() } } impl SignalDelivery { pub fn new() -> SignalDelivery { SignalDelivery { pending: 0, actions: DEFAULT_ACTIONS, } } pub fn get_action(&self, signal: Signal) -> SigAction { self.actions[signal as usize] } pub fn set_action(&mut self, signal: Signal, action: SigAction) -> KResult<()> { if signal > SIGMAX { kbail!(EINVAL, "set_action(): signal out of range"); } self.actions[signal as usize] = action; Ok(()) } pub fn is_pending(&self) -> bool { self.pending != 0 } pub fn signal(&mut self, signal: Signal) { self.pending |= 1 << signal } pub fn pop_pending(&mut self) -> Option<(Signal, SigAction)> { if self.pending == 0 { return None; } let signal = self.pending.trailing_zeros(); self.pending &= !(1 << signal); Some((signal as Signal, self.actions[signal as usize])) } } pub type SigSet = BitArray<[u8; 8], LocalBits>; #[derive(Clone, Copy, Debug)] pub enum SignalMask { Block, Unblock, Set, } ================================================ FILE: src/task/vmem.rs ================================================ use core::sync::atomic::AtomicUsize; use alloc::vec::Vec; use x86::controlregs::cr3; use x86_64::structures::{idt::PageFaultErrorCode, paging::PageTableFlags}; use crate::{ arch::idt::InterruptErrorFrame, backtrace, fs::{ opened_file::{FileDesc, OpenFlags}, FileRef, }, kbail, kerror, mem::{ addr::VirtAddr, addr_space::AddressSpace, allocator::{alloc_kernel_frames, free_kernel_frames, PageAllocator}, consts::{PAGE_SIZE, USER_STACK_TOP, USER_VALLOC_BASE}, paging::{ mapper::Mapper, units::{AllocatedFrames, Frame, FrameRange, MemoryUnit, Page, PageRange}, }, }, task::{current_task, get_scheduler, signal::SIGSEGV}, userland::buffer::UserBufferMut, util::{align_up, KResult}, }; bitflags::bitflags! { #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub struct MMapProt: u64 { const PROT_READ = 0x1; const PROT_WRITE = 0x2; const PROT_EXEC = 0x4; const PROT_NONE = 0x0; } } bitflags::bitflags! { #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub struct MMapFlags: u64 { const MAP_PRIVATE = 0x02; const MAP_FIXED = 0x10; const MAP_ANONYMOUS = 0x20; } } impl From for PageTableFlags { fn from(e: MMapProt) -> Self { let mut res = PageTableFlags::empty(); res.insert(PageTableFlags::PRESENT); res.insert(PageTableFlags::USER_ACCESSIBLE); if !e.contains(MMapProt::PROT_EXEC) { res.insert(PageTableFlags::NO_EXECUTE); } if e.contains(MMapProt::PROT_WRITE) { res.insert(PageTableFlags::WRITABLE); } res } } #[derive(Clone)] pub enum MMapKind { Anonymous, File { file: FileRef, offset: usize, size: usize, }, } #[derive(Clone)] pub struct VmemArea { start_addr: VirtAddr, end_addr: VirtAddr, flags: MMapFlags, pub(crate) prot: MMapProt, kind: MMapKind, } impl VmemArea { pub const fn new( start_addr: VirtAddr, end_addr: VirtAddr, flags: MMapFlags, prot: MMapProt, kind: MMapKind, ) -> Self { Self { start_addr, end_addr, flags, prot, kind, } } pub const fn null() -> Self { Self { start_addr: VirtAddr::null(), end_addr: VirtAddr::null(), flags: MMapFlags::empty(), prot: MMapProt::PROT_NONE, kind: MMapKind::Anonymous, } } pub fn contains_addr(&self, addr: VirtAddr) -> bool { (self.start_addr..self.end_addr).contains(&addr) } pub fn overlaps_range(&self, start: VirtAddr, end: VirtAddr) -> bool { self.contains_addr(start) || self.contains_addr(end) } pub fn start_address(&self) -> VirtAddr { self.start_addr } pub fn end_address(&self) -> VirtAddr { self.end_addr } pub fn size_in_bytes(&self) -> usize { self.end_addr.value() - self.start_addr.value() } pub fn merge_with(&mut self, other: Self) -> KResult<()> { if other.size_in_bytes() == 0 { return Ok(()); } if other.start_addr != self.end_addr && other.end_addr != self.start_addr { kbail!("Cannot merge non-contiguous areas"); } if other.start_addr < self.start_addr { self.start_addr = other.start_addr; } if other.end_addr > self.end_addr { self.end_addr = other.end_addr; } Ok(()) } } pub struct Vmem { areas: Vec, next_id: AtomicUsize, page_allocator: PageAllocator, } impl Vmem { pub fn new() -> Self { let mut page_allocator = PageAllocator::new_vec(); unsafe { page_allocator.insert_free_region(PageRange::new( Page::containing_address(VirtAddr::new(PAGE_SIZE)), Page::containing_address(USER_STACK_TOP), )) } Self { areas: Vec::new(), next_id: AtomicUsize::new(0), page_allocator, } } pub fn area_containing_mut( &mut self, start_addr: VirtAddr, end_addr: VirtAddr, ) -> Option<&mut VmemArea> { self.areas .iter_mut() .find(|area| area.contains_addr(start_addr) && area.contains_addr(end_addr)) } pub fn area_containing(&self, start_addr: VirtAddr, end_addr: VirtAddr) -> Option<&VmemArea> { self.areas .iter() .find(|area| area.contains_addr(start_addr) && area.contains_addr(end_addr)) } pub fn add_area( &mut self, start_addr: VirtAddr, end_addr: VirtAddr, flags: MMapFlags, prot: MMapProt, kind: MMapKind, ) -> KResult<()> { if self.area_containing_mut(start_addr, end_addr).is_some() { self.log(); panic!("Cannot add vmem area that already exists"); } self.areas.push(VmemArea { start_addr, end_addr, flags, prot, kind, }); self.areas.retain(|a| a.start_addr != a.end_addr); self.areas.sort_by_key(|area| area.start_address().value()); self.merge_contiguous_chunks(); Ok(()) } pub fn merge_contiguous_chunks(&mut self) { let mut i = 0; while i < self.areas.len() - 1 { let mut j = i + 1; while j < self.areas.len() { if self.areas[i] .overlaps_range(self.areas[j].start_address(), self.areas[j].end_address()) { let old = self.areas.remove(j); self.areas[i].merge_with(old).expect("Error merging pages"); } else { j += 1; } } i += 1; } } fn zero_memory(&self, start_addr: VirtAddr, end_addr: VirtAddr) -> KResult<()> { unsafe { start_addr.fill(0, end_addr.value() - start_addr.value()) }?; Ok(()) } pub fn mprotect( &mut self, start_addr: VirtAddr, size: usize, protection: MMapProt, ) -> KResult<()> { let area = self .area_containing_mut(start_addr, start_addr + size - 1) .ok_or(kerror!(ENOMEM, "mprotect(): no areas containing address"))?; area.prot = protection; Ok(()) } pub fn mremap( &mut self, old_addr: VirtAddr, old_size: usize, new_size: usize, active_mapper: &mut Mapper, ) -> KResult { if new_size == 0 { return Err(kerror!(EINVAL, "mremap(): new_size is zero")); } // let new_size_aligned = align_up(new_size, PAGE_SIZE); let conflicting_area = self .area_containing(old_addr + new_size, old_addr + new_size) .cloned(); let old_area = self.area_containing_mut(old_addr, old_addr + old_size); if let Some(old_area) = old_area { if let Some(ref conflicting_area) = conflicting_area { if conflicting_area.start_addr == old_area.start_addr { if old_area.end_address() < old_addr + new_size { old_area.end_addr = old_addr + new_size; } return Ok(old_addr); } } else { old_area.end_addr = old_area.start_addr + new_size; return Ok(old_area.start_addr); } } else { kbail!(EFAULT, "mremap(): address not owned by task"); } self.log(); let old_area = self.area_containing(old_addr, old_addr).unwrap().clone(); let VmemArea { start_addr, end_addr, flags, prot, kind: _, } = old_area; let new_addr = self.mmap( VirtAddr::null(), new_size, prot, flags, -1, 0, active_mapper, )?; let old_pages = PageRange::new( Page::containing_address(start_addr), Page::containing_address(end_addr), ); let new_pages = PageRange::new( Page::containing_address(new_addr), Page::containing_address(new_addr + old_area.size_in_bytes()), ); for (old_page, new_page) in old_pages.iter().zip(new_pages.iter()) { let frame = active_mapper.translate(old_page.start_address()); if let Some((frame, flags)) = frame { active_mapper .map_to_single(new_page, Frame::containing_address(frame), flags) .unwrap(); } // else, do nothing? } self.munmap(active_mapper, start_addr, end_addr)?; Ok(new_addr) } #[allow(clippy::too_many_arguments)] pub fn mmap( &mut self, start_addr: VirtAddr, size: usize, protection: MMapProt, flags: MMapFlags, _fd: FileDesc, _offset: usize, active_mapper: &mut Mapper, ) -> KResult { if size == 0 { kbail!(EINVAL, "mmap(): size is zero"); } if flags.contains(MMapFlags::MAP_FIXED) { if start_addr.align_down(PAGE_SIZE) != start_addr { kbail!(EINVAL, "mmap(): start_addr not page-aligned"); } let end_addr = start_addr + size; if end_addr.align_up(PAGE_SIZE) != end_addr { kbail!(EINVAL, "mmap(): end_addr not page-aligned"); } if self.area_containing(start_addr, end_addr).is_some() { kbail!(ENOMEM, "mmap(): address already in use"); } self.map_area( start_addr, end_addr, flags, protection, MMapKind::Anonymous, active_mapper, )?; return Ok(start_addr); } let size_aligned = align_up(size, PAGE_SIZE); if start_addr == VirtAddr::null() { let start = self.find_free_space_above(USER_VALLOC_BASE, size_aligned); if let Some((start, prev_idx)) = start { if let Some(prev_idx) = prev_idx { let prev = &mut self.areas[prev_idx]; if prev.end_addr == start && prev.prot == protection { assert_eq!(prev.flags, flags); assert!(matches!(prev.kind, MMapKind::Anonymous)); prev.end_addr = start + size_aligned; return Ok(start); } else { log::warn!( "Couldn't merge area [{:?} .. {:?}] with [{:?} .. {:?}]", prev.start_addr, prev.end_addr, start, start + size_aligned ); } } self.add_area( start, start + size_aligned, flags, protection, MMapKind::Anonymous, )?; return Ok(start); } self.log(); kbail!(ENOMEM, "mmap(): no free space big enough"); } kbail!(ENOSYS, "mmap(): not yet implemented for start_addr != null"); } // pub fn brk(&mut self, active_mapper: &mut Mapper, new_brk: VirtAddr) -> KResult { // let current_brk = self // .areas // .iter() // .find(|area| matches!(area.kind, MMapKind::Anonymous)) // .map(|area| area.end_addr) // .unwrap_or(USER_VALLOC_BASE); // Ok(current_brk) // } fn find_free_space_above( &mut self, minimum_start: VirtAddr, size: usize, ) -> Option<(VirtAddr, Option)> { if self.areas.is_empty() { return Some((minimum_start, None)); } assert!(self.areas.is_sorted_by_key(|a| a.start_addr)); for i in 0..self.areas.len() - 1 { if self.areas[i + 1].start_addr >= minimum_start + size && self.areas[i + 1].start_addr.value() - self.areas[i].end_addr.value() >= size { if self.areas[i].end_addr < minimum_start { return Some((minimum_start, Some(i))); } else { return Some((self.areas[i].end_addr, Some(i))); } } } None } pub fn map_area( &mut self, start_addr: VirtAddr, end_addr: VirtAddr, flags: MMapFlags, prot: MMapProt, kind: MMapKind, active_mapper: &mut Mapper, ) -> KResult<()> { let count = PageRange::new( Page::containing_address(start_addr), Page::containing_address(end_addr), ) .size_in_pages(); let ap = self .page_allocator .allocate_at(Page::containing_address(start_addr), count)?; let _mp = active_mapper.map(ap, prot.into())?; self.add_area( start_addr.align_down(PAGE_SIZE), end_addr.align_up(PAGE_SIZE), flags, prot, kind, )?; Ok(()) } unsafe fn do_unmap( &mut self, start_addr: VirtAddr, end_addr: VirtAddr, active_mapper: &mut Mapper, ) -> Option<()> { let range = PageRange::new( Page::containing_address(start_addr), Page::containing_address(end_addr), ); unsafe { self.page_allocator.insert_free_region(range) } for page in range.iter() { unsafe { if let Some(frame) = active_mapper.unmap_single(page) { free_kernel_frames( &mut AllocatedFrames::assume_allocated(FrameRange::new(frame, frame)), false, ) .ok(); } else { // log::warn!("Tried to free memory that wasn't mapped: {:?}", page); } }; } // KERNEL_FRAME_ALLOCATOR // .get() // .unwrap() // .lock() // .merge_contiguous_chunks(); Some(()) } pub fn munmap( &mut self, active_mapper: &mut Mapper, start_addr: VirtAddr, end_addr: VirtAddr, ) -> KResult<()> { let area_idx = self .areas .iter_mut() .enumerate() .find(|(_idx, area)| area.contains_addr(start_addr)) .map(|(idx, _area)| idx) .ok_or(kerror!(EINVAL, "munmap(): address range not owned by task"))?; let area_clone = self.areas[area_idx].clone(); if start_addr <= area_clone.start_addr && end_addr >= area_clone.end_addr { // remove the whole area and continue recursively unmapping until the whole range is unmapped unsafe { self.do_unmap(area_clone.start_addr, area_clone.end_addr, active_mapper); } self.areas.remove(area_idx); self.munmap(active_mapper, area_clone.end_addr, end_addr)?; } else if start_addr >= area_clone.start_addr && end_addr < area_clone.end_addr { // split the area in two unsafe { self.do_unmap(start_addr, end_addr, active_mapper); } self.areas.remove(area_idx); assert!(!matches!(area_clone.kind, MMapKind::File { .. })); // todo: handle this self.add_area( area_clone.start_addr, start_addr, area_clone.flags, area_clone.prot, area_clone.kind.clone(), )?; self.add_area( end_addr, area_clone.end_addr, area_clone.flags, area_clone.prot, area_clone.kind, )?; } else if start_addr <= area_clone.start_addr && end_addr < area_clone.end_addr { // replace the end of the area (start was unmapped) assert!(!matches!(area_clone.kind, MMapKind::File { .. })); // todo: handle this unsafe { self.do_unmap(area_clone.start_addr, end_addr, active_mapper); } self.areas[area_idx].start_addr = end_addr; } else if start_addr > area_clone.start_addr && end_addr >= area_clone.end_addr { // replace the start of the area (end was unmapped) unsafe { self.do_unmap(start_addr, area_clone.end_addr, active_mapper); } self.areas[area_idx].end_addr = end_addr; } else { unreachable!() } Ok(()) } pub fn clear(&mut self, active_mapper: &mut Mapper) { for id in 0..self.next_id.load(core::sync::atomic::Ordering::Acquire) { if let Some(area) = self.areas.get(id) { unsafe { self.do_unmap(area.start_addr, area.end_addr, active_mapper); } } } self.areas.clear(); } pub fn log(&self) { log::debug!("BEGIN VIRTUAL MEMORY STATE DUMP"); for area in self.areas.iter() { log::debug!( "{:>16x?} .. {:>16x?} | {:?} {:?}", area.start_addr, area.end_addr, area.flags, area.prot ); } log::debug!("END VIRTUAL MEMORY STATE DUMP"); } pub fn fork_from(&mut self, parent: &Vmem) { self.areas = parent.areas.clone(); // self.mp = parent.mp.clone(); self.page_allocator = parent.page_allocator.clone(); self.next_id.store( parent.next_id.load(core::sync::atomic::Ordering::Acquire), core::sync::atomic::Ordering::Release, ); } pub fn handle_page_fault( &mut self, process_addr_space: &mut AddressSpace, faulted_addr: VirtAddr, stack_frame: InterruptErrorFrame, reason: PageFaultErrorCode, ) -> KResult<()> { let dump_and_exit = || { let current = current_task(); log::error!("PID: {}", current.pid().as_usize()); log::error!("Instruction pointer: {:#x}", { stack_frame.frame.rip }); log::error!( "Process page table: {:#x}", process_addr_space.cr3().value() ); log::error!("Current page table: {:#x}", unsafe { cr3() }); log::error!("Faulted address: {:?}", faulted_addr); log::error!("Reason: {:?}", reason); log::debug!("{:#x?}", stack_frame); self.log(); backtrace::unwind_user_stack_from(stack_frame.frame.rbp, stack_frame.frame.rip); get_scheduler().send_signal_to(current, SIGSEGV); get_scheduler().exit_current(1) }; // log::debug!("User page fault at {:#x}", { stack_frame.frame.rip }); // log::debug!("PID: {}", current_task().pid().as_usize()); // log::debug!("Faulted address: {:?}", faulted_addr); // log::debug!("Reason: {:?}", reason); // self.log(); // backtrace::unwind_user_stack_from(stack_frame.frame.rbp, stack_frame.frame.rip); if faulted_addr.align_down(PAGE_SIZE) == VirtAddr::null() { log::error!("User segmentation fault: null pointer access"); dump_and_exit() } let mut faulted_area = None; for area in self.areas.iter() { if area.contains_addr(faulted_addr) { faulted_area = Some(area); break; } } if let Some(area) = faulted_area { if !reason.contains(PageFaultErrorCode::PROTECTION_VIOLATION) { // allocate and map pages let page = Page::containing_address(faulted_addr); let ap = self.page_allocator.allocate_at(page, 1)?; process_addr_space.with_mapper(|mut mapper| mapper.map(ap, area.prot.into()))?; match &area.kind { MMapKind::Anonymous => { self.zero_memory(page.start_address(), page.start_address() + PAGE_SIZE)?; } MMapKind::File { file, offset, size } => { let size = size.min(&PAGE_SIZE); let mut buf = alloc::vec![0; *size]; let user_buf = UserBufferMut::from_slice(&mut buf); let read = file.read(*offset, user_buf, &OpenFlags::empty())?; if read == 0 { self.zero_memory( page.start_address(), page.start_address() + PAGE_SIZE, )?; } else { unsafe { page.start_address().write_bytes_user(&buf)?; } } } } return Ok(()); } else if reason.contains(PageFaultErrorCode::CAUSED_BY_WRITE) { if !area.prot.contains(MMapProt::PROT_WRITE) { log::error!("User segmentation fault: illegal write"); dump_and_exit() } // COW let new_frame = alloc_kernel_frames(1)?; let new_page = unsafe { core::slice::from_raw_parts_mut( new_frame .start_address() .as_hhdm_virt() .as_raw_ptr_mut::(), PAGE_SIZE, ) }; let old_page = unsafe { core::slice::from_raw_parts( faulted_addr.align_down(PAGE_SIZE).as_raw_ptr::(), PAGE_SIZE, ) }; new_page.copy_from_slice(old_page); process_addr_space.with_mapper(|mut mapper| -> KResult<()> { unsafe { mapper.unmap_single(Page::containing_address(faulted_addr)); } mapper.map_to_single( Page::containing_address(faulted_addr), new_frame.start(), area.prot.into(), )?; Ok(()) })?; return Ok(()); } unreachable!( "handle_page_fault(): faulted area found, but was already readable and writable" ) } else { log::error!("User segmentation fault: illegal access"); dump_and_exit() } log::warn!("Unrecoverable page fault at {:#x}", { stack_frame.frame.rip }); kbail!(EFAULT, "handle_page_fault(): couldn't handle page fault"); } } impl Default for Vmem { fn default() -> Self { Self::new() } } ================================================ FILE: src/task/wait_queue.rs ================================================ use alloc::{collections::VecDeque, sync::Arc}; use crate::{ arch, kbail, util::{IrqMutex, KResult}, }; use super::{current_task, get_scheduler, Task, TaskState}; pub struct WaitQueue { pub(super) queue: IrqMutex>>, } impl Default for WaitQueue { fn default() -> Self { Self::new() } } impl WaitQueue { pub const fn new() -> WaitQueue { WaitQueue { queue: IrqMutex::new(VecDeque::new()), } } pub fn sleep_signalable_until( &self, timeout: Option, mut sleep_if_none: F, ) -> KResult where F: FnMut() -> KResult>, { let start_time = arch::time::get_uptime_ms(); loop { let current = current_task(); let scheduler = get_scheduler(); current.set_state(TaskState::Waiting); { let mut q_lock = self.queue.lock(); if !q_lock.iter().any(|t| t.pid == current.pid) { q_lock.push_back(current.clone()); } } if current.has_pending_signals() { scheduler.resume_task(current.clone()); self.queue.lock().retain(|t| t.pid != current.pid); kbail!( EINTR, "sleep_signalable_until(): interrupted by pending signals" ); } let ret_value = match sleep_if_none() { Ok(Some(ret_val)) => Some(Ok(ret_val)), Ok(None) => None, Err(err) => Some(Err(err)), }; let current = current_task(); if let Some(ret_val) = ret_value { scheduler.resume_task(current.clone()); self.queue.lock().retain(|t| t.pid != current.pid); return ret_val; } scheduler.sleep(timeout)?; if let Some(timeout) = timeout { if arch::time::get_uptime_ms() >= start_time + timeout { kbail!(EINTR, "sleep_signalable_until(): timeout reached"); } } } } } ================================================ FILE: src/userland/buffer.rs ================================================ use core::{mem::size_of, ops::Add}; use alloc::string::{String, ToString}; use crate::{ kbail, kerror, mem::{addr::VirtAddr, addr_space::TmpAddrSpaceGuard}, task::current_task, util::{align_up, error::KResult}, }; #[allow(dead_code)] enum Inner<'a> { Slice(&'a [u8]), User { base: VirtAddr, len: usize }, } pub struct UserBuffer<'a> { inner: Inner<'a>, } impl<'a> UserBuffer<'a> { pub fn from_vaddr(vaddr: VirtAddr, len: usize) -> UserBuffer<'static> { UserBuffer { inner: Inner::User { base: vaddr, len }, } } pub fn from_slice(slc: &'a [u8]) -> UserBuffer<'a> { UserBuffer { inner: Inner::Slice(slc), } } #[allow(clippy::len_without_is_empty)] pub fn len(&self) -> usize { match &self.inner { Inner::Slice(slice) => slice.len(), Inner::User { len, .. } => *len, } } } #[allow(dead_code)] enum InnerMut<'a> { Slice(&'a mut [u8]), User { base: VirtAddr, len: usize }, } pub struct UserBufferMut<'a> { inner: InnerMut<'a>, } impl<'a> UserBufferMut<'a> { pub fn from_slice(slice: &'a mut [u8]) -> UserBufferMut<'a> { UserBufferMut { inner: InnerMut::Slice(slice), } } pub fn from_vaddr(vaddr: VirtAddr, len: usize) -> UserBufferMut<'static> { UserBufferMut { inner: InnerMut::User { base: vaddr, len }, } } #[allow(clippy::len_without_is_empty)] pub fn len(&self) -> usize { match &self.inner { InnerMut::Slice(slice) => slice.len(), InnerMut::User { len, .. } => *len, } } } pub unsafe fn user_strncpy_rust(dst: *mut u8, src: *const u8, max_len: usize) -> usize { let mut read_len = 0usize; loop { let byte = unsafe { src.add(read_len).read_volatile() }; if byte == b'\0' || read_len > max_len { break; } unsafe { dst.add(read_len).write_volatile(byte) }; read_len += 1; } read_len } pub struct CStr { string: String, } impl CStr { pub fn new(vaddr: VirtAddr, max_len: usize, is_user: bool) -> KResult { vaddr.align_ok::()?; if is_user { (vaddr + max_len).user_ok()?; } let mut tmp = alloc::vec![0; max_len]; let guard = current_task().arch_mut().address_space.temporarily_switch(); let read_len = unsafe { user_strncpy_rust(tmp.as_mut_ptr(), vaddr.as_raw_ptr(), max_len) }; drop(guard); let string = core::str::from_utf8(&tmp[..read_len]) .map_err(|_| kerror!(EINVAL, "UserCStr: UTF-8 parsing error"))? .to_string(); Ok(CStr { string }) } pub fn as_bytes(&self) -> &[u8] { self.string.as_bytes() } pub fn as_str(&self) -> &str { &self.string } } pub struct UserBufferReader<'a> { buf: UserBuffer<'a>, pos: usize, #[allow(dead_code)] guard: TmpAddrSpaceGuard, } impl<'a> UserBufferReader<'a> { pub fn from_vaddr(buf: VirtAddr, len: usize) -> UserBufferReader<'a> { let guard = current_task().arch_mut().address_space.temporarily_switch(); UserBufferReader { buf: UserBuffer::from_vaddr(buf, len), pos: 0, guard, } } pub fn from_buf(buf: UserBuffer<'a>) -> UserBufferReader<'a> { let guard = current_task().arch_mut().address_space.temporarily_switch(); UserBufferReader { buf, pos: 0, guard } } pub fn read_len(&self) -> usize { self.pos } pub fn skip(&mut self, len: usize) -> KResult<()> { self.check_remaining_len(len)?; self.pos += len; Ok(()) } pub fn read_bytes(&mut self, dst: &mut [u8]) -> KResult { let read_len = core::cmp::min(dst.len(), self.remaining_len()); if read_len == 0 { return Ok(0); } match &self.buf.inner { Inner::Slice(src) => { dst[..read_len].copy_from_slice(&src[self.pos..(self.pos + read_len)]) } Inner::User { base, .. } => unsafe { base.add(self.pos).read_bytes(&mut dst[..read_len])?; }, } self.pos += read_len; Ok(read_len) } pub fn read(&mut self) -> KResult { self.check_remaining_len(size_of::())?; let val = match &self.buf.inner { Inner::Slice(src) => { // this could cause a page fault if the inner slice of the buffer isn't mapped to the current page table! unsafe { *(src.as_ptr().add(self.pos) as *const T) } } Inner::User { base, .. } => unsafe { base.add(self.pos).read()? }, }; self.pos += size_of::(); Ok(val) } fn check_remaining_len(&self, len: usize) -> KResult<()> { if len <= self.remaining_len() { Ok(()) } else { kbail!(EINVAL, "check_remaining_len(): len out of bounds"); } } pub fn remaining_len(&self) -> usize { self.buf.len() - self.pos } } pub struct UserBufferWriter<'a> { buf: UserBufferMut<'a>, pos: usize, #[allow(dead_code)] guard: TmpAddrSpaceGuard, } impl<'a> UserBufferWriter<'a> { pub fn from_vaddr(buf: VirtAddr, len: usize) -> UserBufferWriter<'a> { let guard = current_task().arch_mut().address_space.temporarily_switch(); UserBufferWriter { buf: UserBufferMut::from_vaddr(buf, len), pos: 0, guard, } } pub fn from_buf(buf: UserBufferMut<'a>) -> UserBufferWriter<'a> { let guard = current_task().arch_mut().address_space.temporarily_switch(); UserBufferWriter { buf, pos: 0, guard } } pub fn written_len(&self) -> usize { self.pos } pub fn write(&mut self, value: T) -> KResult { let bytes = unsafe { core::slice::from_raw_parts(&value as *const T as *const u8, size_of::()) }; self.write_bytes(bytes) } pub fn write_bytes(&mut self, src: &[u8]) -> KResult { let copy_len = core::cmp::min(self.remaining_len(), src.len()); if copy_len == 0 { return Ok(0); } self.check_remaining_len(copy_len)?; match &mut self.buf.inner { InnerMut::Slice(dst) => { dst[self.pos..(self.pos + copy_len)].copy_from_slice(&src[..copy_len]); } InnerMut::User { base, .. } => { unsafe { base.add(self.pos).write_bytes(&src[..copy_len]) }?; } } self.pos += copy_len; Ok(copy_len) } pub fn skip_until_alignment(&mut self, alignment: usize) -> KResult<()> { let new_pos = align_up(self.pos, alignment); self.check_remaining_len(new_pos - self.pos)?; self.pos = new_pos; Ok(()) } pub fn fill(&mut self, value: u8, len: usize) -> KResult<()> { self.check_remaining_len(len)?; match &mut self.buf.inner { InnerMut::Slice(dst) => { dst[self.pos..(self.pos + len)].fill(value); } InnerMut::User { base, .. } => { unsafe { base.add(self.pos).fill(value, len) }?; } } self.pos += len; Ok(()) } pub fn write_bytes_or_zeros(&mut self, buf: &[u8], max_len: usize) -> KResult<()> { let zero_start = core::cmp::min(buf.len(), max_len); self.check_remaining_len(zero_start)?; self.write_bytes(&buf[..zero_start])?; self.fill(0, max_len - zero_start)?; Ok(()) } fn check_remaining_len(&self, len: usize) -> KResult<()> { if len <= self.remaining_len() { Ok(()) } else { kbail!(EINVAL, "check_remaining_len(): len out of bounds"); } } pub fn remaining_len(&self) -> usize { self.buf.len() - self.pos } } ================================================ FILE: src/userland/elf.rs ================================================ use alloc::{borrow::ToOwned, string::String, vec::Vec}; use elfloader::{ElfBinary, ElfLoader, Entry}; use x86::random::rdrand_slice; use xmas_elf::{ program::Type, sections::{SectionData, ShType}, }; use crate::{ fs::{initramfs::get_root, opened_file::OpenFlags, path::Path, FileRef}, kerror, mem::{ addr::VirtAddr, addr_space::AddressSpace, allocator::alloc_kernel_frames, consts::PAGE_SIZE, }, task::vmem::{MMapFlags, MMapKind, MMapProt, Vmem}, userland::buffer::UserBufferMut, util::{align_up, KResult}, }; pub fn gen_stack_canary() -> [u8; 16] { let mut random_bytes = [0u8; 16]; unsafe { rdrand_slice(&mut random_bytes) }; random_bytes } #[repr(u64)] #[derive(Debug, Copy, Clone)] pub enum AuxvType { AtNull = 0, AtPhdr = 3, AtPhEnt = 4, AtPhNum = 5, AtEntry = 9, } #[derive(Clone)] pub struct SymTabEntry { pub name: String, pub value: u64, pub size: u64, } pub struct UserlandEntry { pub entry_point: VirtAddr, pub vmem: Vmem, pub fsbase: Option, pub addr_space: AddressSpace, pub hdr: [(AuxvType, usize); 4], pub symtab: Option>, } pub fn load_elf(file: FileRef) -> KResult { let len = file.stat()?.size.0 as usize; let current = AddressSpace::current(); // let mut buf = alloc::vec![0u8; len]; let mut addr_space = AddressSpace::new()?; addr_space.switch(); let frames = alloc_kernel_frames(align_up(len, PAGE_SIZE) / PAGE_SIZE)?; // let mp = addr_space.mapper().map(pages, PageTableFlags::PRESENT | PageTableFlags::WRITABLE)?; let buf = unsafe { core::slice::from_raw_parts_mut( frames.start_address().as_hhdm_virt().as_raw_ptr_mut(), frames.size_in_bytes(), ) }; let ubuf = UserBufferMut::from_slice(buf); file.read(0, ubuf, &OpenFlags::empty())?; current.switch(); let elf = ElfBinary::new(buf).map_err(|_e| kerror!("load_elf(): elf loader error"))?; let mut start_of_image = usize::MAX; let mut end_of_image = 0; for hdr in elf.program_headers() { if hdr.get_type().unwrap() == xmas_elf::program::Type::Load { end_of_image = end_of_image.max((hdr.virtual_addr() + hdr.mem_size()) as usize); start_of_image = end_of_image.min(hdr.virtual_addr() as usize); } } let mut symbol_table = None; for section in elf.file.section_iter() { if section.get_type() == Ok(ShType::SymTab) { let section_data = section.get_data(&elf.file); if let Ok(ref _section_data @ SectionData::SymbolTable64(symtab)) = section_data { symbol_table = Some( symtab .iter() .map(|e| SymTabEntry { name: e.get_name(&elf.file).unwrap().to_owned(), size: e.size(), value: e.value(), }) .collect::>(), ); } } } if symbol_table.is_none() { log::warn!("Couldn't get symbol table for ELF."); } log::debug!( "ELF loaded into memory at {:#x} .. {:#x}", start_of_image, end_of_image ); if elf.is_pie() { log::warn!("It's a PIE"); } let mut vmem = Vmem::new(); let load_offset = if elf.file.header.pt2.type_().as_type() == xmas_elf::header::Type::SharedObject { 0x40000000 } else { 0 }; let entry_point = VirtAddr::new(elf.entry_point() as usize + load_offset); log::debug!("Entry point: {:?}", entry_point); addr_space.switch(); let mut loader = KadosElfLoader { vmem: &mut vmem, addr_space: &mut addr_space, base_addr: usize::MAX, load_offset, file: file.clone(), entry_point, }; elf.load(&mut loader).unwrap(); let p2 = elf.file.header.pt2; log::debug!("Base address at {:?}", VirtAddr::new(loader.base_addr)); let hdr = [ (AuxvType::AtPhdr, p2.ph_offset() as usize + loader.base_addr), (AuxvType::AtPhEnt, p2.ph_entry_size() as usize), (AuxvType::AtPhNum, p2.ph_count() as usize), (AuxvType::AtEntry, p2.entry_point() as usize), ]; if let Some(ref symtab) = symbol_table { for sym in symtab.iter() { if sym.name == "__stack_chk_fail" { // unsafe { // *(sym.value as *mut u8) = 0xc3; // "ret" instruction // } log::warn!("SSP is ON for this binary!"); break; } } } current.switch(); log::debug!("ELF load complete."); Ok(UserlandEntry { entry_point: loader.entry_point, vmem, fsbase: None, addr_space, hdr, symtab: symbol_table, }) } struct KadosElfLoader<'a> { vmem: &'a mut Vmem, addr_space: &'a mut AddressSpace, base_addr: usize, load_offset: usize, file: FileRef, entry_point: VirtAddr, } impl ElfLoader for KadosElfLoader<'_> { fn allocate( &mut self, load_headers: elfloader::LoadableHeaders, ) -> Result<(), elfloader::ElfLoaderErr> { for header in load_headers { if header.get_type().unwrap() == Type::Load { let start = VirtAddr::new(header.virtual_addr() as usize + self.load_offset) .align_down(PAGE_SIZE); let mem_end = VirtAddr::new( header.virtual_addr() as usize + header.mem_size() as usize + self.load_offset, ) .align_up(PAGE_SIZE); if start.value() < self.base_addr { self.base_addr = start.value(); } let flags = MMapFlags::empty(); let mut prot = MMapProt::PROT_WRITE; if header.flags().is_execute() { prot.insert(MMapProt::PROT_EXEC); } let kind = MMapKind::File { file: self.file.clone(), offset: header.offset() as usize, size: header.file_size() as usize, }; log::debug!("Mapping region {:?} .. {:?}", start, mem_end); self.addr_space.with_mapper(|mut mapper| { self.vmem .map_area(start, mem_end, flags, prot, kind, &mut mapper) .unwrap(); }); } else if header.get_type().unwrap() == Type::Interp { let ld = get_root() .unwrap() .lookup(Path::new("/usr/lib/ld.so"), true) .unwrap() .as_file() .unwrap() .clone(); let res = load_elf(ld).unwrap(); self.entry_point = res.entry_point; } } Ok(()) } fn load( &mut self, flags: elfloader::Flags, base: elfloader::VAddr, region: &[u8], ) -> Result<(), elfloader::ElfLoaderErr> { let region_start = VirtAddr::new(base as usize + self.load_offset); let region_end = region_start + region.len(); let area = self .vmem .area_containing_mut(region_start, region_end) .unwrap(); let mut prot = MMapProt::empty(); if flags.is_read() { prot |= MMapProt::PROT_READ; } if flags.is_write() { prot |= MMapProt::PROT_WRITE; } if flags.is_execute() { prot |= MMapProt::PROT_EXEC; } // this should be safe since the pages should already be mapped and writable in allocate() unsafe { region_start.write_bytes(region).unwrap() }; // set the correct protections now area.prot = prot; Ok(()) } fn tls( &mut self, tdata_start: elfloader::VAddr, _tdata_length: u64, _total_size: u64, _align: u64, ) -> Result<(), elfloader::ElfLoaderErr> { log::warn!( "TLS section found at {:?}", VirtAddr::new(tdata_start as usize) ); Ok(()) } fn relocate( &mut self, entry: elfloader::RelocationEntry, ) -> Result<(), elfloader::ElfLoaderErr> { use elfloader::arch::x86_64::RelocationTypes; match entry.rtype { elfloader::RelocationType::x86_64(rtype) => match rtype { RelocationTypes::R_AMD64_RELATIVE => { let reloc_value = entry.addend.unwrap() as usize + self.load_offset; log::trace!( "Applying relocation R_AMD64_RELATIVE at location {:#x} -> {:#x}", entry.offset, reloc_value ); unsafe { *((entry.offset + self.load_offset as u64) as *mut usize) = reloc_value; } } rtype => { log::error!("Unsupported relocation type: {:?}", rtype); return Err(elfloader::ElfLoaderErr::UnsupportedRelocationEntry); } }, _ => return Err(elfloader::ElfLoaderErr::UnsupportedArchitecture), } Ok(()) } } ================================================ FILE: src/userland/mod.rs ================================================ pub mod buffer; pub mod elf; pub mod syscall; ================================================ FILE: src/userland/syscall/mod.rs ================================================ use alloc::borrow::ToOwned; use crate::{ arch::idt::InterruptFrame, fs::{ opened_file::{FileDesc, OpenFlags}, path::{Path, PathBuf}, FileMode, }, kerror, mem::addr::VirtAddr, task::{ current_task, get_scheduler, group::PgId, vmem::{MMapFlags, MMapProt}, TaskId, }, userland::syscall::syscall_impl::task::WaitOptions, util::{ ctypes::{c_int, c_nfds}, KError, KResult, }, }; use super::buffer::CStr; pub mod syscall_impl; pub fn errno_to_isize(res: &Result>) -> isize { match res { Ok(retval) => *retval, Err(err) => { let errno = err.errno().unwrap() as i32; -errno as isize } } } pub const QUIET_SYSCALLS: &[usize] = &[ SYS_UNLINK, SYS_CLOCK_GETTIME, SYS_NANOSLEEP, SYS_LSEEK, SYS_WRITEV, SYS_READV, SYS_GETRANDOM, SYS_WRITE, SYS_READ, SYS_OPEN, SYS_STAT, ]; pub struct SyscallHandler<'a> { pub frame: &'a mut InterruptFrame, } impl SyscallHandler<'_> { #[allow(clippy::too_many_arguments)] pub fn dispatch( &mut self, a1: usize, a2: usize, a3: usize, a4: usize, a5: usize, a6: usize, n: usize, ) -> Result> { let rip = self.frame.rip; let quiet = QUIET_SYSCALLS.contains(&n); if !quiet { let current = current_task(); let symtab = ¤t.arch_mut().symtab; if let Some(symtab) = symtab { let mut symbol = None; for sym in symtab.iter() { if rip as u64 >= sym.value && rip as u64 <= (sym.value + sym.size) { symbol = Some(sym.name.to_owned()); break; } } if let Some(symbol) = symbol { log::trace!( "[{}] SYSCALL #{} {}({:#x}, {:#x}, {:#x}, {:#x}, {:#x}, {:#x})", symbol, n, syscall_name_by_number(n), a1, a2, a3, a4, a5, a6 ); } else { log::trace!( "[{:#x}] SYSCALL #{} {}({:#x}, {:#x}, {:#x}, {:#x}, {:#x}, {:#x})", rip, n, syscall_name_by_number(n), a1, a2, a3, a4, a5, a6 ); } } else { log::trace!( "[{:#x}] SYSCALL #{} {}({:#x}, {:#x}, {:#x}, {:#x}, {:#x}, {:#x})", rip, n, syscall_name_by_number(n), a1, a2, a3, a4, a5, a6 ); } } let res = match n { SYS_ARCH_PRCTL => self.sys_arch_prctl(a1 as i32, VirtAddr::new(a2)), SYS_SET_TID_ADDRESS => self.sys_set_tid_address(VirtAddr::new(a1)), SYS_WRITE => self.sys_write(a1 as FileDesc, VirtAddr::new(a2), a3), SYS_WRITEV => self.sys_writev(a1 as FileDesc, VirtAddr::new(a2), a3), SYS_READV => self.sys_readv(a1 as FileDesc, VirtAddr::new(a2), a3), SYS_READ => self.sys_read(a1 as FileDesc, VirtAddr::new(a2), a3), SYS_IOCTL => self.sys_ioctl(a1 as FileDesc, a2, a3), SYS_RT_SIGPROCMASK => { self.sys_rt_sigprocmask(a1, VirtAddr::new(a2), VirtAddr::new(a3), a4) } SYS_FORK => self.sys_fork(), SYS_WAIT4 => self.sys_wait4( TaskId::new(a1), VirtAddr::new(a2), crate::bitflags_from_user!(WaitOptions, a3 as i32), VirtAddr::new(a4), ), SYS_EXECVE => self.sys_execve(&resolve_path(a1)?, VirtAddr::new(a2), VirtAddr::new(a3)), SYS_GETTID => self.sys_getpid(), // todo SYS_GETPID => self.sys_getpid(), SYS_GETPPID => self.sys_getppid(), SYS_GETPGID => self.sys_getpgid(TaskId::new(a1)), SYS_SETPGID => self.sys_setpgid(TaskId::new(a1), a2 as PgId), SYS_EXIT => self.sys_exit(a1 as c_int), SYS_EXIT_GROUP => self.sys_exit(a1 as c_int), // todo SYS_MMAP => self.sys_mmap( VirtAddr::new(a1), a2, crate::bitflags_from_user!(MMapProt, a3 as u64), crate::bitflags_from_user!(MMapFlags, a4 as u64), a5 as FileDesc, a6, ), SYS_MPROTECT => self.sys_mprotect( VirtAddr::new(a1), a2, crate::bitflags_from_user!(MMapProt, a3 as u64), ), SYS_MUNMAP => self.sys_munmap(VirtAddr::new(a1), a2), // SYS_BRK => self.sys_brk(VirtAddr::new(a1)), SYS_MREMAP => self.sys_mremap(VirtAddr::new(a1), a2, a3), SYS_RT_SIGACTION => { self.sys_rt_sigaction(a1 as c_int, VirtAddr::new(a2), VirtAddr::new(a3)) } SYS_GETUID => Ok(0), // TODO: SYS_GETEUID => Ok(0), // TODO: SYS_SETUID => Ok(0), // TODO: SYS_SETGID => Ok(0), // TODO: SYS_SETGROUPS => Ok(0), // TODO: SYS_STAT => self.sys_stat(&resolve_path(a1)?, VirtAddr::new(a2)), SYS_LSTAT => self.sys_lstat(&resolve_path(a1)?, VirtAddr::new(a2)), SYS_FSTAT => self.sys_fstat(a1 as FileDesc, VirtAddr::new(a2)), SYS_OPEN => self.sys_open( &resolve_path(a1)?, crate::bitflags_from_user!(OpenFlags, a2 as i32), FileMode::new(a3 as u32), ), SYS_GETCWD => self.sys_getcwd(VirtAddr::new(a1), a2), SYS_GETDENTS64 => self.sys_getdents64(a1 as FileDesc, VirtAddr::new(a2), a3), SYS_FCNTL => self.sys_fcntl(a1 as FileDesc, a2 as c_int, a3), SYS_UNAME => self.sys_uname(VirtAddr::new(a1)), SYS_CLOSE => self.sys_close(a1 as FileDesc), SYS_POLL => self.sys_poll(VirtAddr::new(a1), a2 as c_nfds, a3 as c_int), SYS_CHDIR => self.sys_chdir(&resolve_path(a1)?), SYS_RT_SIGRETURN => self.sys_rt_sigreturn(), SYS_PIPE => self.sys_pipe(VirtAddr::new(a1)), SYS_CLONE => self.sys_clone( a1, VirtAddr::new(a2), a3, VirtAddr::new(a4), a5, VirtAddr::new(a6), ), SYS_KILL => self.sys_kill(TaskId::new(a1), a2 as c_int), SYS_TKILL => self.sys_kill(TaskId::new(a1), a2 as c_int), // todo SYS_UNLINK => self.sys_unlink(&resolve_path(a1)?), SYS_LSEEK => self.sys_lseek(a1 as FileDesc, a2, a3.into()), SYS_DUP2 => self.sys_dup2(a1 as FileDesc, a2 as FileDesc), SYS_CLOCK_GETTIME => self.sys_clock_gettime(a1, VirtAddr::new(a2)), SYS_NANOSLEEP => self.sys_nanosleep(VirtAddr::new(a1), VirtAddr::new(a2)), SYS_MKDIR => self.sys_mkdir(&resolve_path(a1)?, FileMode::new(a2 as u32)), SYS_GETRANDOM => self.sys_getrandom(VirtAddr::new(a1), a2), SYS_SOCKET => self.sys_socket(a1, a2, a3), SYS_SETSOCKOPT => self.sys_setsockopt( a1 as FileDesc, a2 as c_int, a3 as c_int, VirtAddr::new(a4), a5, ), SYS_MADVISE => Ok(0), // todo _ => Err(kerror!( ENOSYS, "SyscallHandler::dispatch(): syscall not implemented" )), }; if let Err(err) = get_scheduler().try_delivering_signal(self.frame, errno_to_isize(&res)) { if !quiet { log::error!("Failed to send signal: {:?}", err); } } res } } #[macro_export] macro_rules! bitflags_from_user { ($st:tt, $input:expr) => {{ let bits = $input; match $st::from_bits(bits) { Some(flags) => Ok(flags)?, None => { log::warn!( concat!("unsupported bitflags for ", stringify!($st), ": {:#x}"), bits ); Err($crate::kerror!( ENOSYS, "bitflags_from_user(): unsupported bitflags" ))? } } }}; } #[inline] fn resolve_path(uaddr: usize) -> KResult { Ok(Path::new(CStr::new(VirtAddr::new(uaddr), 512, false)?.as_str()).into()) } pub fn syscall_name_by_number(n: usize) -> &'static str { match n { 0 => "read", 1 => "write", 2 => "open", 3 => "close", 4 => "stat", 5 => "fstat", 6 => "lstat", 7 => "poll", 8 => "lseek", 9 => "mmap", 10 => "mprotect", 11 => "munmap", 12 => "brk", 13 => "rt_sigaction", 14 => "rt_sigprocmask", 15 => "rt_sigreturn", 16 => "ioctl", 17 => "pread64", 18 => "pwrite64", 19 => "readv", 20 => "writev", 21 => "access", 22 => "pipe", 23 => "select", 24 => "sched_yield", 25 => "mremap", 26 => "msync", 27 => "mincore", 28 => "madvise", 29 => "shmget", 30 => "shmat", 31 => "shmctl", 32 => "dup", 33 => "dup2", 34 => "pause", 35 => "nanosleep", 36 => "getitimer", 37 => "alarm", 38 => "setitimer", 39 => "getpid", 40 => "sendfile", 41 => "socket", 42 => "connect", 43 => "accept", 44 => "sendto", 45 => "recvfrom", 46 => "sendmsg", 47 => "recvmsg", 48 => "shutdown", 49 => "bind", 50 => "listen", 51 => "getsockname", 52 => "getpeername", 53 => "socketpair", 54 => "setsockopt", 55 => "getsockopt", 56 => "clone", 57 => "fork", 58 => "vfork", 59 => "execve", 60 => "exit", 61 => "wait4", 62 => "kill", 63 => "uname", 64 => "semget", 65 => "semop", 66 => "semctl", 67 => "shmdt", 68 => "msgget", 69 => "msgsnd", 70 => "msgrcv", 71 => "msgctl", 72 => "fcntl", 73 => "flock", 74 => "fsync", 75 => "fdatasync", 76 => "truncate", 77 => "ftruncate", 78 => "getdents", 79 => "getcwd", 80 => "chdir", 81 => "fchdir", 82 => "rename", 83 => "mkdir", 84 => "rmdir", 85 => "creat", 86 => "link", 87 => "unlink", 88 => "symlink", 89 => "readlink", 90 => "chmod", 91 => "fchmod", 92 => "chown", 93 => "fchown", 94 => "lchown", 95 => "umask", 96 => "gettimeofday", 97 => "getrlimit", 98 => "getrusage", 99 => "sysinfo", 100 => "times", 101 => "ptrace", 102 => "getuid", 103 => "syslog", 104 => "getgid", 105 => "setuid", 106 => "setgid", 107 => "geteuid", 108 => "getegid", 109 => "setpgid", 110 => "getppid", 111 => "getpgrp", 112 => "setsid", 113 => "setreuid", 114 => "setregid", 115 => "getgroups", 116 => "setgroups", 117 => "setresuid", 118 => "getresuid", 119 => "setresgid", 120 => "getresgid", 121 => "getpgid", 122 => "setfsuid", 123 => "setfsgid", 124 => "getsid", 125 => "capget", 126 => "capset", 127 => "rt_sigpending", 128 => "rt_sigtimedwait", 129 => "rt_sigqueueinfo", 130 => "rt_sigsuspend", 131 => "sigaltstack", 132 => "utime", 133 => "mknod", 134 => "uselib", 135 => "personality", 136 => "ustat", 137 => "statfs", 138 => "fstatfs", 139 => "sysfs", 140 => "getpriority", 141 => "setpriority", 142 => "sched_setparam", 143 => "sched_getparam", 144 => "sched_setscheduler", 145 => "sched_getscheduler", 146 => "sched_get_priority_max", 147 => "sched_get_priority_min", 148 => "sched_rr_get_interval", 149 => "mlock", 150 => "munlock", 151 => "mlockall", 152 => "munlockall", 153 => "vhangup", 154 => "modify_ldt", 155 => "pivot_root", 156 => "_sysctl", 157 => "prctl", 158 => "arch_prctl", 159 => "adjtimex", 160 => "setrlimit", 161 => "chroot", 162 => "sync", 163 => "acct", 164 => "settimeofday", 165 => "mount", 166 => "umount2", 167 => "swapon", 168 => "swapoff", 169 => "reboot", 170 => "sethostname", 171 => "setdomainname", 172 => "iopl", 173 => "ioperm", 174 => "create_module", 175 => "init_module", 176 => "delete_module", 177 => "get_kernel_syms", 178 => "query_module", 179 => "quotactl", 180 => "nfsservctl", 181 => "getpmsg", 182 => "putpmsg", 183 => "afs_syscall", 184 => "tuxcall", 185 => "security", 186 => "gettid", 187 => "readahead", 188 => "setxattr", 189 => "lsetxattr", 190 => "fsetxattr", 191 => "getxattr", 192 => "lgetxattr", 193 => "fgetxattr", 194 => "listxattr", 195 => "llistxattr", 196 => "flistxattr", 197 => "removexattr", 198 => "lremovexattr", 199 => "fremovexattr", 200 => "tkill", 201 => "time", 202 => "futex", 203 => "sched_setaffinity", 204 => "sched_getaffinity", 205 => "set_thread_area", 206 => "io_setup", 207 => "io_destroy", 208 => "io_getevents", 209 => "io_submit", 210 => "io_cancel", 211 => "get_thread_area", 212 => "lookup_dcookie", 213 => "epoll_create", 214 => "epoll_ctl_old", 215 => "epoll_wait_old", 216 => "remap_file_pages", 217 => "getdents64", 218 => "set_tid_address", 219 => "restart_syscall", 220 => "semtimedop", 221 => "fadvise64", 222 => "timer_create", 223 => "timer_settime", 224 => "timer_gettime", 225 => "timer_getoverrun", 226 => "timer_delete", 227 => "clock_settime", 228 => "clock_gettime", 229 => "clock_getres", 230 => "clock_nanosleep", 231 => "exit_group", 232 => "epoll_wait", 233 => "epoll_ctl", 234 => "tgkill", 235 => "utimes", 236 => "vserver", 237 => "mbind", 238 => "set_mempolicy", 239 => "get_mempolicy", 240 => "mq_open", 241 => "mq_unlink", 242 => "mq_timedsend", 243 => "mq_timedreceive", 244 => "mq_notify", 245 => "mq_getsetattr", 246 => "kexec_load", 247 => "waitid", 248 => "add_key", 249 => "request_key", 250 => "keyctl", 251 => "ioprio_set", 252 => "ioprio_get", 253 => "inotify_init", 254 => "inotify_add_watch", 255 => "inotify_rm_watch", 256 => "migrate_pages", 257 => "openat", 258 => "mkdirat", 259 => "mknodat", 260 => "fchownat", 261 => "futimesat", 262 => "newfstatat", 263 => "unlinkat", 264 => "renameat", 265 => "linkat", 266 => "symlinkat", 267 => "readlinkat", 268 => "fchmodat", 269 => "faccessat", 270 => "pselect6", 271 => "ppoll", 272 => "unshare", 273 => "set_robust_list", 274 => "get_robust_list", 275 => "splice", 276 => "tee", 277 => "sync_file_range", 278 => "vmsplice", 279 => "move_pages", 280 => "utimensat", 281 => "epoll_pwait", 282 => "signalfd", 283 => "timerfd_create", 284 => "eventfd", 285 => "fallocate", 286 => "timerfd_settime", 287 => "timerfd_gettime", 288 => "accept4", 289 => "signalfd4", 290 => "eventfd2", 291 => "epoll_create1", 292 => "dup3", 293 => "pipe2", 294 => "inotify_init1", 295 => "preadv", 296 => "pwritev", 297 => "rt_tgsigqueueinfo", 298 => "perf_event_open", 299 => "recvmmsg", 300 => "fanotify_init", 301 => "fanotify_mark", 302 => "prlimit64", 303 => "name_to_handle_at", 304 => "open_by_handle_at", 305 => "clock_adjtime", 306 => "syncfs", 307 => "sendmmsg", 308 => "setns", 309 => "getcpu", 310 => "process_vm_readv", 311 => "process_vm_writev", 312 => "kcmp", 313 => "finit_module", 314 => "sched_setattr", 315 => "sched_getattr", 316 => "renameat2", 317 => "seccomp", 318 => "getrandom", 319 => "memfd_create", 320 => "kexec_file_load", 321 => "bpf", 322 => "execveat", 323 => "userfaultfd", 324 => "membarrier", 325 => "mlock2", 326 => "copy_file_range", 327 => "preadv2", 328 => "pwritev2", 329 => "pkey_mprotect", 330 => "pkey_alloc", 331 => "pkey_free", 332 => "statx", 333 => "io_pgetevents", 334 => "rseq", 51729 => "kados_debug", _ => "(unknown)", } } pub const SYS_READ: usize = 0; pub const SYS_WRITE: usize = 1; pub const SYS_OPEN: usize = 2; pub const SYS_CLOSE: usize = 3; pub const SYS_STAT: usize = 4; pub const SYS_FSTAT: usize = 5; pub const SYS_LSTAT: usize = 6; pub const SYS_POLL: usize = 7; pub const SYS_LSEEK: usize = 8; pub const SYS_MMAP: usize = 9; pub const SYS_MPROTECT: usize = 10; pub const SYS_MUNMAP: usize = 11; pub const SYS_BRK: usize = 12; pub const SYS_RT_SIGACTION: usize = 13; pub const SYS_RT_SIGPROCMASK: usize = 14; pub const SYS_RT_SIGRETURN: usize = 15; pub const SYS_IOCTL: usize = 16; pub const SYS_READV: usize = 19; pub const SYS_WRITEV: usize = 20; pub const SYS_PIPE: usize = 22; pub const SYS_SELECT: usize = 23; pub const SYS_MREMAP: usize = 25; pub const SYS_MADVISE: usize = 28; pub const SYS_DUP2: usize = 33; pub const SYS_NANOSLEEP: usize = 35; pub const SYS_GETPID: usize = 39; pub const SYS_SOCKET: usize = 41; pub const SYS_CONNECT: usize = 42; pub const SYS_ACCEPT: usize = 43; pub const SYS_SENDTO: usize = 44; pub const SYS_RECVFROM: usize = 45; pub const SYS_SHUTDOWN: usize = 48; pub const SYS_BIND: usize = 49; pub const SYS_LISTEN: usize = 50; pub const SYS_GETSOCKNAME: usize = 51; pub const SYS_GETPEERNAME: usize = 52; pub const SYS_SETSOCKOPT: usize = 54; pub const SYS_GETSOCKOPT: usize = 55; pub const SYS_CLONE: usize = 56; pub const SYS_FORK: usize = 57; pub const SYS_EXECVE: usize = 59; pub const SYS_EXIT: usize = 60; pub const SYS_WAIT4: usize = 61; pub const SYS_KILL: usize = 62; pub const SYS_UNAME: usize = 63; pub const SYS_FCNTL: usize = 72; pub const SYS_FSYNC: usize = 74; pub const SYS_GETCWD: usize = 79; pub const SYS_CHDIR: usize = 80; pub const SYS_MKDIR: usize = 83; pub const SYS_LINK: usize = 86; pub const SYS_UNLINK: usize = 87; pub const SYS_READLINK: usize = 89; pub const SYS_CHMOD: usize = 90; pub const SYS_CHOWN: usize = 92; pub const SYS_GETUID: usize = 102; pub const SYS_SYSLOG: usize = 103; pub const SYS_SETUID: usize = 105; pub const SYS_SETGID: usize = 106; pub const SYS_GETEUID: usize = 107; pub const SYS_SETPGID: usize = 109; pub const SYS_GETPPID: usize = 110; pub const SYS_GETPGRP: usize = 111; pub const SYS_GETPGID: usize = 121; pub const SYS_SETGROUPS: usize = 116; pub const SYS_ARCH_PRCTL: usize = 158; pub const SYS_REBOOT: usize = 169; pub const SYS_GETTID: usize = 186; pub const SYS_TKILL: usize = 200; pub const SYS_GETDENTS64: usize = 217; pub const SYS_SET_TID_ADDRESS: usize = 218; pub const SYS_CLOCK_GETTIME: usize = 228; pub const SYS_EXIT_GROUP: usize = 231; pub const SYS_UTIMES: usize = 235; pub const SYS_LINKAT: usize = 265; pub const SYS_GETRANDOM: usize = 318; ================================================ FILE: src/userland/syscall/syscall_impl/fs.rs ================================================ use core::{mem::size_of, ops::Add}; use alloc::{borrow::ToOwned, string::String, sync::Arc}; use x86::random::rdrand_slice; use crate::{ bitflags_from_user, fs::{ alloc_inode_no, initramfs::{dir::InitRamFsDir, file::InitRamFsFile}, opened_file::{FileDesc, LseekWhence, OpenFlags}, path::Path, FileMode, INode, PollStatus, O_RDWR, O_WRONLY, POLL_WAIT_QUEUE, S_IFDIR, S_IFREG, }, kbail, kerror, mem::addr::VirtAddr, task::current_task, userland::{ buffer::{UserBuffer, UserBufferMut, UserBufferReader, UserBufferWriter}, syscall::SyscallHandler, }, util::{ align_up, ctypes::{c_int, c_nfds, c_short}, errno::Errno, KResult, }, }; pub const F_DUPFD: c_int = 0; pub const F_GETFD: c_int = 1; pub const F_SETFD: c_int = 2; pub const F_GETFL: c_int = 3; pub const F_SETFL: c_int = 4; pub const F_SETLK: c_int = 6; // Linux-specific commands. pub const F_LINUX_SPECIFIC_BASE: c_int = 1024; pub const F_DUPFD_CLOEXEC: c_int = F_LINUX_SPECIFIC_BASE + 6; impl SyscallHandler<'_> { pub fn sys_fcntl(&mut self, fd: FileDesc, cmd: c_int, arg: usize) -> KResult { let current = current_task(); let mut files = current.opened_files.lock(); match cmd { F_GETFL => { let flags = files.get(fd)?.get_flags(); Ok(flags.bits() as isize) } F_SETFD => { files.get(fd)?.set_close_on_exec(arg == 1); Ok(0) } F_SETFL => { files .get(fd)? .set_flags(OpenFlags::from_bits_truncate(arg as i32))?; Ok(0) } F_GETFD => Ok(0), F_DUPFD_CLOEXEC => { let fd = files.dup(fd, Some(arg as i32), OpenFlags::O_CLOEXEC)?; Ok(fd as isize) } F_SETLK => Ok(0), _ => Err(kerror!(ENOSYS, "sys_fctnl(): unknown command")), } } pub fn sys_getcwd(&mut self, buf: VirtAddr, len: usize) -> KResult { let cwd = current_task().root_fs.lock().cwd_path().resolve_abs_path(); if len < cwd.as_str().len() { return Err(kerror!(ERANGE, "sys_getcwd(): buffer too small")); } let mut cwd = String::from(cwd.as_str()); cwd.push('\0'); let buf_val = buf.value(); let ubuf = UserBufferMut::from_vaddr(buf, len); let mut writer = UserBufferWriter::from_buf(ubuf); writer.write_bytes(cwd.as_bytes()).unwrap(); // this currently never returns Err; may change Ok(buf_val as isize) } pub fn sys_getdents64( &mut self, fd: FileDesc, dir_ptr: VirtAddr, len: usize, ) -> KResult { let current = current_task(); let opened_files = current.opened_files.lock(); let dir = opened_files.get(fd)?; let mut writer = UserBufferWriter::from_vaddr(dir_ptr, len); while let Some(entry) = dir.readdir()? { let alignment = size_of::(); let record_len = align_up( size_of::() * 2 + size_of::() + 1 + entry.name.len() + 1, alignment, ); if writer.written_len() + record_len > len { break; } writer.write(entry.inode_no as u64)?; writer.write(dir.pos() as u64)?; writer.write(record_len as u16)?; writer.write(entry.file_type as u8)?; writer.write_bytes(entry.name.as_bytes())?; writer.write(0u8)?; writer.skip_until_alignment(alignment)?; } Ok(writer.written_len() as isize) } pub fn sys_chdir(&mut self, path: &Path) -> KResult { current_task().root_fs.lock().chdir(path)?; Ok(0) } pub fn sys_ioctl(&mut self, fd: FileDesc, cmd: usize, arg: usize) -> KResult { let opened_file = current_task().get_opened_file_by_fd(fd)?; opened_file.ioctl(cmd, arg) } pub fn sys_getrandom(&mut self, buf: VirtAddr, bufflen: usize) -> KResult { let mut v = alloc::vec![0u8; bufflen]; unsafe { rdrand_slice(&mut v); } unsafe { buf.write_bytes_user(&v) }?; Ok(bufflen as isize) } } fn create(path: &Path, _flags: OpenFlags, mode: FileMode) -> KResult { // if flags.contains(OpenFlags::O_DIRECTORY) { // return Err(errno!(Errno::EINVAL, "create(): invalid flags")); // } let (parent_dir, name) = path .parent_and_basename() .ok_or(kerror!(EINVAL, "create(): invalid path"))?; let current = current_task(); let root = current.root_fs.lock(); let inode = if mode.is_regular_file() { INode::File(Arc::new(InitRamFsFile::new( name.to_owned(), alloc_inode_no(), ))) } else if mode.is_directory() { INode::Dir(Arc::new(InitRamFsDir::new( name.to_owned(), alloc_inode_no(), ))) } else { return Err(kerror!(EINVAL, "create(): invalid flags")); }; root.lookup(parent_dir, true)? .as_dir()? .insert(inode.clone()); Ok(inode) } impl SyscallHandler<'_> { pub fn sys_open(&mut self, path: &Path, flags: OpenFlags, mode: FileMode) -> KResult { let current = current_task(); // log::trace!("Attempting to open {}", path); if flags.contains(OpenFlags::O_CREAT) { match create(path, flags, FileMode::new(S_IFREG | mode.access_mode())) { Ok(_) => {} Err(err) if err.errno() == Some(Errno::EINVAL) => {} Err(err) if flags.contains(OpenFlags::O_EXCL) && err.errno() == Some(Errno::EEXIST) => {} Err(err) => return Err(err), } } let root = current.root_fs.lock(); let mut opened_files = current.opened_files.lock(); let path_comp = root.lookup_path(path, true)?; if flags.contains(OpenFlags::O_DIRECTORY) && !path_comp.inode.is_dir() { kbail!(ENOTDIR, "sys_open(): not a directory"); } let access_mode = mode.access_mode(); if path_comp.inode.is_dir() && (access_mode == O_WRONLY || access_mode == O_RDWR) { kbail!(EISDIR, "sys_open(): is a directory"); } let fd = opened_files.open(path_comp, flags)?; log::trace!("Opened {} as {}.", path, fd); Ok(fd as isize) } pub fn sys_close(&mut self, fd: FileDesc) -> KResult { let current = current_task(); current.opened_files.lock().close(fd)?; log::trace!("Closed {}", fd); Ok(0) } pub fn sys_mkdir(&mut self, path: &Path, mode: FileMode) -> KResult { create( path, OpenFlags::empty(), FileMode::new(S_IFDIR | mode.access_mode()), )?; Ok(0) } pub fn sys_pipe(&mut self, fds: VirtAddr) -> KResult { if fds == VirtAddr::null() { kbail!(EINVAL, "sys_pipe(): fds was NULL"); } let current = current_task(); let pipe = current.opened_files.lock().open_pipe(OpenFlags::empty())?; let write_fd = pipe.write_fd(); let read_fd = pipe.read_fd(); let mut writer = UserBufferWriter::from_vaddr(fds, size_of::() * 2); writer.write(write_fd)?; writer.write(read_fd)?; Ok(0) } pub fn sys_unlink(&mut self, path: &Path) -> KResult { if path.is_empty() { kbail!(EINVAL, "sys_unlink(): path was empty"); } let current = current_task(); let root = current.root_fs.lock(); // log::debug!("Attempting to unlink {}", path); let path_component = root.lookup_path(path, true)?; path_component .parent_dir .as_ref() .unwrap() .inode .as_dir() .unwrap() .unlink(&path_component.name)?; Ok(0) } } impl SyscallHandler<'_> { pub fn sys_poll(&mut self, fds: VirtAddr, nfds: c_nfds, timeout: c_int) -> KResult { let timeout = if timeout >= 0 { Some(timeout as usize) } else { None }; POLL_WAIT_QUEUE.sleep_signalable_until(timeout, || { let mut ready_fds = 0; let fds_len = (nfds as usize) * (size_of::() + 2 * size_of::()); let mut reader = UserBufferReader::from_vaddr(fds, fds_len); for _ in 0..nfds { let fd = reader.read::()?; let events = bitflags_from_user!(PollStatus, reader.read::()?); if fd < 0 { kbail!(EINVAL, "sys_poll(): invalid fd"); } else if events.is_empty() { kbail!(EINVAL, "sys_poll(): invalid events"); } else { let current = current_task(); let opened_files = current.opened_files.lock(); let status = opened_files.get(fd)?.poll()?; let revents = events & status; if !revents.is_empty() { ready_fds += 1; } unsafe { fds.add(reader.read_len()) .write::(revents.bits())?; } reader.skip(size_of::())?; }; } if ready_fds > 0 { Ok(Some(ready_fds)) } else { Ok(None) } }) } pub fn sys_read(&mut self, fd: FileDesc, vaddr: VirtAddr, len: usize) -> KResult { let opened_file = current_task().get_opened_file_by_fd(fd)?; let ubuf = UserBufferMut::from_vaddr(vaddr, len); let read_len = opened_file.read(ubuf)?; // log::debug!("read {}", read_len); Ok(read_len as isize) } pub fn sys_stat(&mut self, path: &Path, buf: VirtAddr) -> KResult { // log::debug!("sys_stat-ing path {}", path); let stat = current_task().root_fs.lock().lookup(path, true)?.stat()?; unsafe { buf.write_user(stat)?; } Ok(0) } pub fn sys_lstat(&mut self, path: &Path, buf: VirtAddr) -> KResult { let stat = current_task().root_fs.lock().lookup(path, false)?.stat()?; unsafe { buf.write_user(stat) }?; Ok(0) } pub fn sys_fstat(&mut self, fd: FileDesc, buf: VirtAddr) -> KResult { let file = current_task().get_opened_file_by_fd(fd)?; let stat = file.path().inode.stat()?; unsafe { buf.write_user(stat) }?; Ok(0) } pub fn sys_write(&mut self, fd: FileDesc, addr: VirtAddr, len: usize) -> KResult { let user_buf = UserBuffer::from_vaddr(addr, len); let file = current_task().get_opened_file_by_fd(fd)?; let written_len = file.write(user_buf)?; Ok(written_len as isize) } } pub const IOV_MAX: usize = 1024; #[repr(C)] #[derive(Clone, Copy)] pub struct IoVec { base: VirtAddr, len: usize, } impl SyscallHandler<'_> { pub fn sys_readv( &mut self, fd: FileDesc, iov_base: VirtAddr, iov_count: usize, ) -> KResult { let iov_count = iov_count.min(IOV_MAX); let file = current_task().get_opened_file_by_fd(fd)?; let mut total: usize = 0; for i in 0..iov_count { let mut iov: IoVec = unsafe { iov_base.add(i * size_of::()).read_user::() }?; match total.checked_add(iov.len) { Some(len) if len > isize::MAX as usize => { iov.len = isize::MAX as usize - total; } None => { iov.len = isize::MAX as usize - total; } _ => {} } if iov.len == 0 { continue; } total += file.read(UserBufferMut::from_vaddr(iov.base, iov.len))?; } Ok(total as isize) } pub fn sys_writev( &mut self, fd: FileDesc, iov_base: VirtAddr, iov_count: usize, ) -> KResult { let iov_count = iov_count.min(IOV_MAX); let file = current_task().get_opened_file_by_fd(fd)?; let mut total: usize = 0; for i in 0..iov_count { let mut iov: IoVec = unsafe { iov_base.add(i * size_of::()).read_user::() }?; match total.checked_add(iov.len) { Some(len) if len > isize::MAX as usize => { iov.len = isize::MAX as usize - total; } None => { iov.len = isize::MAX as usize - total; } _ => {} } if iov.len == 0 { continue; } total += file.write(UserBuffer::from_vaddr(iov.base, iov.len))?; } Ok(total as isize) } pub fn sys_lseek( &mut self, fd: FileDesc, offset: usize, whence: LseekWhence, ) -> KResult { let file = current_task().get_opened_file_by_fd(fd)?; file.lseek(offset, whence).map(|off| off as isize) } pub fn sys_dup2(&mut self, old_fd: FileDesc, new_fd: FileDesc) -> KResult { let current = current_task(); let _old = current.get_opened_file_by_fd(old_fd)?; // now that we know it's valid, check if they're the same (as per the man page) if new_fd == old_fd { return Ok(new_fd as isize); } if let Ok(_existing) = current.get_opened_file_by_fd(new_fd) { let mut files = current.opened_files.lock(); files.close(new_fd)?; } let mut files = current.opened_files.lock(); let newfd_dup = files.dup2(old_fd, new_fd)?; assert_eq!(new_fd, newfd_dup); Ok(new_fd as isize) } pub fn sys_socket(&mut self, domain: usize, typ: usize, protocol: usize) -> KResult { let current = current_task(); let mut files = current.opened_files.lock(); let fd = files.open_socket(domain, typ, protocol)?; Ok(fd as isize) } pub fn sys_setsockopt( &mut self, fd: FileDesc, level: c_int, _option_name: c_int, _option_value: VirtAddr, _option_len: usize, ) -> KResult { let current = current_task(); let _socket = current.get_opened_file_by_fd(fd)?; match level { SOL_SOCKET => { // todo } _ => kbail!(EINVAL, "sys_setsockopt(): unknown level"), } Ok(0) } } const SOL_SOCKET: c_int = 1; ================================================ FILE: src/userland/syscall/syscall_impl/mem.rs ================================================ use crate::{ fs::opened_file::FileDesc, mem::addr::VirtAddr, task::{ current_task, vmem::{MMapFlags, MMapProt}, }, userland::syscall::SyscallHandler, util::KResult, }; impl SyscallHandler<'_> { pub fn sys_mmap( &mut self, addr: VirtAddr, size: usize, prot: MMapProt, flags: MMapFlags, fd: FileDesc, offset: usize, ) -> KResult { if fd as isize != -1 { todo!("mmap file"); } let current = current_task(); let vmem = current.vmem(); current .arch_mut() .address_space .with_mapper(|mut mapper| { vmem.lock() .mmap(addr, size, prot, flags, fd, offset, &mut mapper) }) .map(|addr| addr.value() as isize) } pub fn sys_mprotect(&mut self, addr: VirtAddr, size: usize, prot: MMapProt) -> KResult { current_task().vmem().lock().mprotect(addr, size, prot)?; Ok(0) } // pub fn sys_brk(&mut self, addr: VirtAddr) -> KResult { // let current = current_task(); // let new_addr = current // .arch_mut() // .address_space // .with_mapper(|mut mapper| current.vmem().lock().brk(&mut mapper, addr))?; // Ok(new_addr.value() as isize) // } pub fn sys_munmap(&mut self, addr: VirtAddr, size: usize) -> KResult { let current = current_task(); current.arch_mut().address_space.with_mapper(|mut mapper| { current.vmem().lock().munmap(&mut mapper, addr, addr + size) })?; Ok(0) } pub fn sys_mremap(&mut self, addr: VirtAddr, old_size: usize, size: usize) -> KResult { let current = current_task(); let new_addr = current.arch_mut().address_space.with_mapper(|mut mapper| { current .vmem() .lock() .mremap(addr, old_size, size, &mut mapper) })?; Ok(new_addr.value() as isize) } } ================================================ FILE: src/userland/syscall/syscall_impl/mod.rs ================================================ pub mod fs; pub mod mem; pub mod signal; pub mod sys; pub mod task; pub mod time; ================================================ FILE: src/userland/syscall/syscall_impl/signal.rs ================================================ use crate::{ kbail, kerror, mem::addr::VirtAddr, task::{ current_task, get_scheduler, signal::{SigAction, SignalMask, DEFAULT_ACTIONS, SIG_DFL, SIG_ERR, SIG_IGN}, TaskId, }, userland::syscall::SyscallHandler, util::{ctypes::c_int, error::KResult}, }; impl SyscallHandler<'_> { pub fn sys_rt_sigprocmask( &mut self, how: usize, set: VirtAddr, mut oldset: VirtAddr, length: usize, ) -> KResult { if length != 8 { log::warn!("sys_rt_sigprocmask: length != 8"); } let how = match how { 0 => SignalMask::Block, 1 => SignalMask::Unblock, 2 => SignalMask::Set, _ => kbail!(EINVAL, "sys_rt_sigprocmask(): invalid how"), }; current_task().set_signal_mask(how, set, &mut oldset, length)?; Ok(0) } pub fn sys_rt_sigaction( &mut self, signum: c_int, act: VirtAddr, oldact: VirtAddr, ) -> KResult { if oldact != VirtAddr::null() { let action = current_task().signals.lock().get_action(signum); let action = match action { SigAction::Ignore => SIG_IGN, SigAction::Terminate => SIG_ERR, // todo? SigAction::Handler { handler } => handler as usize, }; unsafe { oldact.write_user(action) }?; } if act != VirtAddr::null() { let handler = unsafe { act.read_user::() }?; let new_action = match handler { SIG_IGN => SigAction::Ignore, SIG_DFL => match DEFAULT_ACTIONS.get(signum as usize) { Some(def) => *def, None => { kbail!(EINVAL, "sys_rt_sigaction(): invalid signal number"); } }, _ => SigAction::Handler { handler: unsafe { core::mem::transmute::(handler) }, }, }; current_task() .signals .lock() .set_action(signum, new_action)?; } Ok(0) } pub fn sys_rt_sigreturn(&mut self) -> KResult { get_scheduler().restore_signaled_user_stack(self.frame); kbail!(EINTR, "sys_rt_sigreturn(): should not return") } pub fn sys_kill(&mut self, pid: TaskId, signum: c_int) -> KResult { let sched = get_scheduler(); sched.send_signal_to( sched .find_task(pid) .ok_or(kerror!(ESRCH, "sys_kill(): pid not found"))?, signum, ); Ok(0) } } ================================================ FILE: src/userland/syscall/syscall_impl/sys.rs ================================================ use crate::{ mem::addr::VirtAddr, userland::{buffer::UserBufferWriter, syscall::SyscallHandler}, util::KResult, }; const UTS_FIELD_LEN: usize = 65; impl SyscallHandler<'_> { pub fn sys_uname(&mut self, buf: VirtAddr) -> KResult { let mut writer = UserBufferWriter::from_vaddr(buf, UTS_FIELD_LEN * 6); writer.write_bytes_or_zeros(b"Linux", UTS_FIELD_LEN)?; writer.write_bytes_or_zeros(b"", UTS_FIELD_LEN)?; writer.write_bytes_or_zeros(b"4.0.0", UTS_FIELD_LEN)?; writer.write_bytes_or_zeros(b"K4DOS", UTS_FIELD_LEN)?; writer.write_bytes_or_zeros(b"", UTS_FIELD_LEN)?; writer.write_bytes_or_zeros(b"", UTS_FIELD_LEN)?; Ok(0) } } ================================================ FILE: src/userland/syscall/syscall_impl/task.rs ================================================ use core::{mem::size_of, ops::Add}; use alloc::{sync::Arc, vec::Vec}; use bitflags::bitflags; use crate::{ arch::time, fs::path::Path, kbail, kerror, mem::addr::VirtAddr, task::{current_task, get_scheduler, group::PgId, Task, TaskId, TaskState, JOIN_WAIT_QUEUE}, userland::{buffer::CStr, syscall::SyscallHandler}, util::{ctypes::c_int, errno::Errno, KResult}, }; use super::time::TimeSpec; const ARG_MAX: usize = 512; const ARG_LEN_MAX: usize = 4096; const ENV_MAX: usize = 512; const ENV_LEN_MAX: usize = 4096; impl SyscallHandler<'_> { pub fn sys_arch_prctl(&mut self, code: i32, uaddr: VirtAddr) -> KResult { arch_prctl(current_task(), code, uaddr)?; Ok(0) } pub fn sys_fork(&mut self) -> KResult { let current = current_task(); let _guard = current.arch_mut().address_space.temporarily_switch(); let child = current.fork(); Ok(child.pid().as_usize() as isize) } pub fn sys_clone( &mut self, _clone_flags: usize, user_stack: VirtAddr, r8: usize, args: VirtAddr, r9: usize, entry_point: VirtAddr, ) -> KResult { let child = current_task().clone_process(entry_point, user_stack, args, r8, r9, self.frame); Ok(child.pid().as_usize() as isize) } pub fn sys_execve( &mut self, path: &Path, argv_addr: VirtAddr, envp_addr: VirtAddr, ) -> KResult { let current = current_task(); let _guard = current.arch_mut().address_space.temporarily_switch(); log::debug!("Statting path {}", path); let exefile = current .root_fs .lock() .lookup(path, true)? .as_file()? .clone(); let mut argv = Vec::new(); for i in 0..ARG_MAX { let ptr = argv_addr.add(i * size_of::()); let str_ptr = unsafe { ptr.read_user::() }?; if str_ptr != 0 { argv.push(CStr::new(VirtAddr::new(str_ptr), ARG_LEN_MAX, false)?); } else { break; } } let mut envp = Vec::new(); for i in 0..ENV_MAX { let ptr = envp_addr.add(i * size_of::()); let str_ptr = unsafe { ptr.read_user::() }?; if str_ptr != 0 { envp.push(CStr::new(VirtAddr::new(str_ptr), ENV_LEN_MAX, false)?); } else { break; } } let argv: Vec<&[u8]> = argv.as_slice().iter().map(|s| s.as_bytes()).collect(); let envp: Vec<&[u8]> = envp.as_slice().iter().map(|s| s.as_bytes()).collect(); current.exec(exefile, &argv, &envp)?; Ok(0) } pub fn sys_exit(&mut self, status: c_int) -> KResult { get_scheduler().exit_current(status); Ok(0) } pub fn sys_set_tid_address(&mut self, _addr: VirtAddr) -> KResult { // todo: use addr Ok(current_task().pid().as_usize() as isize) } pub fn sys_getpid(&mut self) -> KResult { Ok(current_task().pid().as_usize() as isize) } pub fn sys_getppid(&mut self) -> KResult { Ok(current_task().ppid().as_usize() as isize) } pub fn sys_getpgid(&mut self, pid: TaskId) -> KResult { if pid.as_usize() == 0 { Ok(current_task().pgid().unwrap() as isize) } else { Ok(get_scheduler() .find_task(pid) .ok_or(kerror!(ESRCH, "sys_getpgid(): task not found"))? .pgid() .unwrap() as isize) } } pub fn sys_setpgid(&mut self, pid: TaskId, pgid: PgId) -> KResult { if pid.as_usize() == 0 { current_task() .group .borrow_mut() .upgrade() .unwrap() .lock() .set_pgid(pgid); } else { get_scheduler() .find_task(pid) .ok_or(kerror!(ESRCH, "sys_setpgid(): task not found"))? .group .borrow_mut() .upgrade() .unwrap() .lock() .set_pgid(pgid); } Ok(0) } } bitflags! { pub struct WaitOptions: c_int { const WNOHANG = 1; const WUNTRACED = 2; } } impl SyscallHandler<'_> { pub fn sys_wait4( &mut self, pid: TaskId, status: VirtAddr, options: WaitOptions, _rusage: VirtAddr, // could be null ) -> KResult { let (got_pid, status_val) = JOIN_WAIT_QUEUE.sleep_signalable_until(None, || { let current = current_task(); let children = current.children.lock(); if children.is_empty() { kbail!(ECHILD, "sys_wait4(): all subprocesses have exited"); } for child in children.iter() { if pid.as_usize() as isize > 0 && pid != child.pid() { continue; } if pid.as_usize() == 0 { todo!() } if let TaskState::ExitedWith(status_val) = child.get_state() { return Ok(Some((child.pid(), status_val))); } } if options.contains(WaitOptions::WNOHANG) { return Ok(Some((TaskId::new(0), 0))); } Ok(None) })?; log::debug!("wait4: status = {status_val}"); current_task() .children .lock() .retain(|p| p.pid() != got_pid); if status.value() != 0 { unsafe { status.write_user::(status_val) }?; } Ok(got_pid.as_usize() as isize) } pub fn sys_nanosleep(&mut self, req: VirtAddr, _rem: VirtAddr) -> KResult { let req = unsafe { req.read_user::() }?; assert_eq!(req.tv_nsec % 1000000, 0); let duration = req.tv_sec * 1000 + req.tv_nsec / 1000000; #[allow(clippy::redundant_guards)] match get_scheduler().sleep(Some(duration as usize)) { Ok(_) => {} Err(e) if e.errno == Some(Errno::EINTR) => { todo!() // return Err(KError::Errno { errno: Errno::EINTR, msg }) } Err(_) => { todo!() } } Ok(0) } pub fn sys_clock_gettime(&mut self, clk_id: usize, tp: VirtAddr) -> KResult { match clk_id { 0 => { let ts = time::get_rt_clock(); unsafe { tp.write_user(ts) }?; } 1 => { let ts = time::get_rt_clock(); unsafe { tp.write_user(ts) }?; } 2 => { let current = current_task(); let delta_ns = time::get_uptime_ns() - current.start_time.get().unwrap() * 1000000; let delta_sec = delta_ns / 1000000000; let ts = TimeSpec { tv_sec: delta_sec as isize, tv_nsec: (delta_ns % 1000000000) as isize, }; unsafe { tp.write_user(ts) }?; } 3 => { let current = current_task(); let delta_ns = time::get_uptime_ns() - current.start_time.get().unwrap() * 1000000; let delta_sec = delta_ns / 1000000000; let ts = TimeSpec { tv_sec: delta_sec as isize, tv_nsec: (delta_ns % 1000000000) as isize, }; unsafe { tp.write_user(ts) }?; } _ => unreachable!(), } Ok(0) } } fn arch_prctl(current_task: Arc, code: i32, addr: VirtAddr) -> KResult<()> { const ARCH_SET_FS: i32 = 0x1002; match code { ARCH_SET_FS => { current_task.arch_mut().set_fsbase(addr); } _ => kbail!(EINVAL, "arch_prctl(): unknown code"), } Ok(()) } ================================================ FILE: src/userland/syscall/syscall_impl/time.rs ================================================ pub const ITIMER_REAL: usize = 0; pub const ITIMER_VIRTUAL: usize = 1; pub const ITIMER_PROF: usize = 2; #[derive(Default, PartialEq)] #[repr(C)] pub struct TimeVal { pub tv_sec: i64, pub tv_usec: i64, } #[derive(Default, PartialEq)] #[repr(C)] pub struct ITimerVal { pub it_interval: TimeVal, pub it_value: TimeVal, } #[derive(Clone, Copy, Default, PartialEq)] #[repr(C)] pub struct TimeSpec { pub tv_sec: isize, pub tv_nsec: isize, } impl TimeSpec { pub const fn zero() -> Self { TimeSpec { tv_sec: 0, tv_nsec: 0, } } } ================================================ FILE: src/util/ctypes.rs ================================================ #[allow(non_camel_case_types)] pub type c_int16 = i16; #[allow(non_camel_case_types)] pub type c_int32 = i32; #[allow(non_camel_case_types)] pub type c_int64 = i64; #[allow(non_camel_case_types)] pub type c_uint32 = u32; #[allow(non_camel_case_types)] pub type c_uint64 = u64; #[allow(non_camel_case_types)] pub type c_int = c_int32; #[allow(non_camel_case_types)] pub type c_uint = c_uint32; #[allow(non_camel_case_types)] pub type c_short = c_int16; #[allow(non_camel_case_types)] pub type c_long = c_int64; #[allow(non_camel_case_types)] pub type c_ulong = c_uint64; #[allow(non_camel_case_types)] pub type c_time = c_int64; #[allow(non_camel_case_types)] pub type c_suseconds = c_int64; #[allow(non_camel_case_types)] pub type c_clockid = c_int; #[allow(non_camel_case_types)] pub type c_nfds = c_ulong; #[allow(non_camel_case_types)] pub type c_size = c_ulong; #[allow(non_camel_case_types)] pub type c_off = c_uint64; ================================================ FILE: src/util/errno.rs ================================================ #[derive(Debug, Copy, Clone, Eq, PartialEq)] #[repr(i32)] #[allow(unused)] #[allow(clippy::upper_case_acronyms)] pub enum Errno { /// Operation not permitted EPERM = 1, /// No such file or directory ENOENT = 2, /// No such process ESRCH = 3, /// Interrupted system call EINTR = 4, /// I/O error EIO = 5, /// No such device or address ENXIO = 6, /// Argument list too long E2BIG = 7, /// Exec format error ENOEXEC = 8, /// Bad file number EBADF = 9, /// No child processes ECHILD = 10, /// Resource temporarily unavailable EAGAIN = 11, /// Cannot allocate memory ENOMEM = 12, /// Permission denied EACCES = 13, /// Bad address EFAULT = 14, /// Block device required ENOTBLK = 15, /// Device or resource busy EBUSY = 16, /// File exists EEXIST = 17, /// Invalid cross-device link EXDEV = 18, /// No such device ENODEV = 19, /// Not a directory ENOTDIR = 20, /// Is a directory EISDIR = 21, /// Invalid argument EINVAL = 22, /// Too many open files in system ENFILE = 23, /// Too many open files EMFILE = 24, /// Inappropriate ioctl for device ENOTTY = 25, /// Text file busy ETXTBSY = 26, /// File too large EFBIG = 27, /// No space left on device ENOSPC = 28, /// Illegal seek ESPIPE = 29, /// Read-only file system EROFS = 30, /// Too many links EMLINK = 31, /// Broken pipe EPIPE = 32, /// Math argument out of domain of func EDOM = 33, /// Math result not representable ERANGE = 34, /// Resource deadlock would occur EDEADLK = 35, /// File name too long ENAMETOOLONG = 36, /// No locks available ENOLCK = 37, /// Function not implemented ENOSYS = 38, /// Directory not empty ENOTEMPTY = 39, /// Too many levels of symbolic links ELOOP = 40, /// No message of desired type ENOMSG = 42, /// Identifier removed EIDRM = 43, /// Channel number out of range ECHRNG = 44, /// Level 2 not synchronized EL2NSYNC = 45, /// Level 3 halted EL3HLT = 46, /// Level 3 reset EL3RST = 47, /// Link number out of range ELNRNG = 48, /// Protocol driver not attached EUNATCH = 49, /// No CSI structure available ENOCSI = 50, /// Level 2 halted EL2HLT = 51, /// Invalid exchange EBADE = 52, /// Invalid request descriptor EBADR = 53, /// Exchange full EXFULL = 54, /// No anode ENOANO = 55, /// Invalid request code EBADRQC = 56, /// Invalid slot EBADSLT = 57, /// Bad font file format EBFONT = 59, /// Device not a stream ENOSTR = 60, /// No data available ENODATA = 61, /// Timer expired ETIME = 62, /// Out of streams resources ENOSR = 63, /// Machine is not on the network ENONET = 64, /// Package not installed ENOPKG = 65, /// Object is remote EREMOTE = 66, /// Link has been severed ENOLINK = 67, /// Advertise error EADV = 68, /// Srmount error ESRMNT = 69, /// Communication error on send ECOMM = 70, /// Protocol error EPROTO = 71, /// Multihop attempted EMULTIHOP = 72, /// RFS specific error EDOTDOT = 73, /// Not a data message EBADMSG = 74, /// Value too large for defined data type EOVERFLOW = 75, /// Name not unique on network ENOTUNIQ = 76, /// File descriptor in bad state EBADFD = 77, /// Remote address changed EREMCHG = 78, /// Can not access a needed shared library ELIBACC = 79, /// Accessing a corrupted shared library ELIBBAD = 80, /// .lib section in a.out corrupted ELIBSCN = 81, /// Attempting to link in too many shared libraries ELIBMAX = 82, /// Cannot exec a shared library directly ELIBEXEC = 83, /// Illegal byte sequence EILSEQ = 84, /// Interrupted system call should be restarted ERESTART = 85, /// Streams pipe error ESTRPIPE = 86, /// Too many users EUSERS = 87, /// Socket operation on non-socket ENOTSOCK = 88, /// Destination address required EDESTADDRREQ = 89, /// Message too long EMSGSIZE = 90, /// Protocol wrong type for socket EPROTOTYPE = 91, /// Protocol not available ENOPROTOOPT = 92, /// Protocol not supported EPROTONOSUPPORT = 93, /// Socket type not supported ESOCKTNOSUPPORT = 94, /// Operation not supported on transport endpoint EOPNOTSUPP = 95, /// Protocol family not supported EPFNOSUPPORT = 96, /// Address family not supported by protocol EAFNOSUPPORT = 97, /// Address already in use EADDRINUSE = 98, /// Cannot assign requested address EADDRNOTAVAIL = 99, /// Network is down ENETDOWN = 100, /// Network is unreachable ENETUNREACH = 101, /// Network dropped connection because of reset ENETRESET = 102, /// Software caused connection abort ECONNABORTED = 103, /// Connection reset by peer ECONNRESET = 104, /// No buffer space available ENOBUFS = 105, /// Transport endpoint is already connected EISCONN = 106, /// Transport endpoint is not connected ENOTCONN = 107, /// Cannot send after transport endpoint shutdown ESHUTDOWN = 108, /// Too many references: cannot splice ETOOMANYREFS = 109, /// Connection timed out ETIMEDOUT = 110, /// Connection refused ECONNREFUSED = 111, /// Host is down EHOSTDOWN = 112, /// No route to host EHOSTUNREACH = 113, /// Operation already in progress EALREADY = 114, /// Operation now in progress EINPROGRESS = 115, /// Stale file handle ESTALE = 116, /// Structure needs cleaning EUCLEAN = 117, /// Not a XENIX named type file ENOTNAM = 118, /// No XENIX semaphores available ENAVAIL = 119, /// Is a named type file EISNAM = 120, /// Remote I/O error EREMOTEIO = 121, /// Quota exceeded EDQUOT = 122, /// No medium found ENOMEDIUM = 123, /// Wrong medium type EMEDIUMTYPE = 124, /// Operation canceled ECANCELED = 125, /// Required key not available ENOKEY = 126, /// Key has expired EKEYEXPIRED = 127, /// Key has been revoked EKEYREVOKED = 128, /// Key was rejected by service EKEYREJECTED = 129, /// Owner died EOWNERDEAD = 130, /// State not recoverable ENOTRECOVERABLE = 131, /// Operation not possible due to RF-kill ERFKILL = 132, /// Memory page has hardware error EHWPOISON = 133, } ================================================ FILE: src/util/error.rs ================================================ use core::fmt::{Debug, Display}; use super::errno::Errno; pub type KResult = Result>; #[derive(Clone, Default, Debug)] pub struct KError<'a> { pub(crate) msg: Option>, pub(crate) errno: Option, } impl<'a> KError<'a> { pub fn msg(&self) -> Option> { self.msg } pub fn errno(&self) -> Option { self.errno } } impl Display for KError<'_> { fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { match (self.errno, self.msg) { (Some(errno), Some(msg)) => write!(f, "{:?}: {}", errno, msg), (Some(errno), None) => write!(f, "{:?}", errno), (None, Some(msg)) => write!(f, "{}", msg), (None, None) => write!(f, "Unknown error"), } } } #[macro_export] macro_rules! kerror { ($e:ident) => { $crate::util::error::KError { errno: Some($crate::util::errno::Errno::$e), msg: None, } }; ($e:ident, $($tts:tt)*) => { $crate::util::error::KError { errno: Some($crate::util::errno::Errno::$e), msg: Some(format_args!($($tts)*)), } }; ($($tts:tt)*) => { $crate::util::error::KError { errno: None, msg: Some(format_args!($($tts)*)), } }; } #[macro_export] macro_rules! kbail { ($e:ident) => { return Err($crate::kerror!($e)) }; ($e:ident, $($tts:tt)*) => { return Err($crate::kerror!($e, $($tts)*)) }; ($($tts:tt)*) => { return Err($crate::kerror!($($tts)*)) }; } ================================================ FILE: src/util/lock.rs ================================================ use core::mem::ManuallyDrop; use core::ops::{Deref, DerefMut}; use spin::mutex::{SpinMutex, SpinMutexGuard}; use x86::current::rflags::{self, RFlags}; use x86_64::instructions::interrupts; use crate::backtrace; use crate::task::wait_queue::WaitQueue; use super::error::KResult; pub struct SavedInterruptStatus { rflags: RFlags, } impl SavedInterruptStatus { pub fn save() -> SavedInterruptStatus { SavedInterruptStatus { rflags: rflags::read(), } } } impl Drop for SavedInterruptStatus { fn drop(&mut self) { rflags::set(rflags::read() | (self.rflags & rflags::RFlags::FLAGS_IF)); } } pub struct BlockingMutex { queue: WaitQueue, inner: IrqMutex, } impl BlockingMutex { pub const fn new(value: T) -> BlockingMutex { BlockingMutex { queue: WaitQueue::new(), inner: IrqMutex::new(value), } } } impl BlockingMutex { pub fn get_mut(&mut self) -> &mut T { self.inner.get_mut() } pub fn try_lock(&self) -> KResult> { if self.inner.is_locked() { Err(kerror!("Cannot relock BlockingMutex")) // todo: more verbose error message } else { Ok(BlockingMutexGuard { inner: ManuallyDrop::new(self.inner.lock()), }) } } pub fn lock(&self) -> KResult> { let guard = self.queue.sleep_signalable_until(None, || { if let Ok(guard) = self.inner.try_lock() { Ok(Some(BlockingMutexGuard { inner: ManuallyDrop::new(guard), })) } else { Ok(None) } })?; Ok(guard) } pub fn is_locked(&self) -> bool { self.inner.is_locked() } /// # Safety /// See `spin::SpinMutex::force_unlock()` pub unsafe fn force_unlock(&self) { unsafe { self.inner.force_unlock() }; } } unsafe impl Sync for BlockingMutex {} unsafe impl Send for BlockingMutex {} pub struct BlockingMutexGuard<'a, T: ?Sized> { inner: ManuallyDrop>, } impl Drop for BlockingMutexGuard<'_, T> { fn drop(&mut self) { unsafe { ManuallyDrop::drop(&mut self.inner); } } } impl Deref for BlockingMutexGuard<'_, T> { type Target = T; fn deref(&self) -> &T { &self.inner } } impl DerefMut for BlockingMutexGuard<'_, T> { fn deref_mut(&mut self) -> &mut T { &mut self.inner } } pub struct IrqMutex { inner: SpinMutex, } impl IrqMutex { pub const fn new(value: T) -> IrqMutex { IrqMutex { inner: SpinMutex::new(value), } } } impl IrqMutex { pub fn get_mut(&mut self) -> &mut T { self.inner.get_mut() } pub fn try_lock(&self) -> KResult> { if self.inner.is_locked() { Err(kerror!("Cannot relock IrqMutex")) // todo: more verbose error message } else { Ok(self.lock()) } } pub fn lock(&self) -> IrqMutexGuard<'_, T> { if self.inner.is_locked() { serial0_println!( "WARNING: Tried to relock IrqMutex of {}", core::any::type_name::() ); backtrace::unwind_stack().ok(); } let saved_intr_status = SavedInterruptStatus::save(); interrupts::disable(); let guard = self.inner.lock(); IrqMutexGuard { inner: ManuallyDrop::new(guard), saved_intr_status: ManuallyDrop::new(saved_intr_status), } } pub fn is_locked(&self) -> bool { self.inner.is_locked() } /// # Safety /// See `spin::SpinMutex::force_unlock()` pub unsafe fn force_unlock(&self) { unsafe { self.inner.force_unlock() }; } } unsafe impl Sync for IrqMutex {} unsafe impl Send for IrqMutex {} pub struct IrqMutexGuard<'a, T: ?Sized> { inner: ManuallyDrop>, saved_intr_status: ManuallyDrop, } impl Drop for IrqMutexGuard<'_, T> { fn drop(&mut self) { unsafe { ManuallyDrop::drop(&mut self.inner); } unsafe { ManuallyDrop::drop(&mut self.saved_intr_status); } } } impl Deref for IrqMutexGuard<'_, T> { type Target = T; fn deref(&self) -> &T { &self.inner } } impl DerefMut for IrqMutexGuard<'_, T> { fn deref_mut(&mut self) -> &mut T { &mut self.inner } } ================================================ FILE: src/util/mod.rs ================================================ #[macro_use] pub mod error; pub mod ctypes; pub mod errno; pub mod lock; pub mod ringbuffer; pub mod stack; pub use self::error::*; pub use self::lock::*; #[inline] pub const fn align_down(val: usize, align: usize) -> usize { val / align * align } #[inline] pub const fn align_up(val: usize, align: usize) -> usize { val.div_ceil(align) * align } ================================================ FILE: src/util/ringbuffer.rs ================================================ //! The ringbuffer from Kerla. use core::{cmp::min, mem::MaybeUninit, ops::Range, slice}; pub struct RingBuffer { buf: [MaybeUninit; CAP], rp: usize, wp: usize, full: bool, } impl Default for RingBuffer { fn default() -> Self { RingBuffer { buf: unsafe { MaybeUninit::uninit().assume_init() }, rp: 0, wp: 0, full: false, } } } impl RingBuffer { pub fn new() -> RingBuffer { Self::default() } pub fn is_writable(&self) -> bool { !self.full } pub fn is_readable(&self) -> bool { self.full || self.rp != self.wp } pub fn push(&mut self, data: T) -> Result<(), T> where T: Copy, { if self.push_slice(&[data]) == 0 { Err(data) } else { Ok(()) } } pub fn pop(&mut self) -> Option where T: Copy, { self.pop_slice(1).map(|slice| slice[0]) } pub fn push_slice(&mut self, data: &[T]) -> usize where T: Copy, { if !self.is_writable() || data.is_empty() { return 0; } let written_len = if self.wp >= self.rp { let free1 = self.wp..CAP; let free2 = 0..self.rp; let src1 = &data[..min(data.len(), free1.len())]; let src2 = &data[src1.len()..min(data.len(), src1.len() + free2.len())]; let dst1 = free1.start..(free1.start + src1.len()); let dst2 = free2.start..(free2.start + src2.len()); self.slice_mut(dst1).copy_from_slice(src1); self.slice_mut(dst2).copy_from_slice(src2); src1.len() + src2.len() } else { let free = self.wp..self.rp; let src = &data[..min(data.len(), free.len())]; let dst = free.start..(free.start + src.len()); self.slice_mut(dst).copy_from_slice(src); src.len() }; self.wp = (self.wp + written_len) % CAP; self.full = self.wp == self.rp; written_len } pub fn pop_slice(&mut self, len: usize) -> Option<&[T]> { if !self.is_readable() { return None; } let range = if self.rp < self.wp { self.rp..min(self.rp + len, self.wp) } else { self.wp..min(self.wp + len, CAP) }; self.rp = (self.rp + range.len()) % CAP; self.full = false; Some(self.slice(range)) } fn slice(&self, range: Range) -> &[T] { debug_assert!(range.end <= CAP); unsafe { let ptr = self.buf.as_ptr() as *const T; slice::from_raw_parts(ptr.add(range.start), range.end - range.start) } } fn slice_mut(&mut self, range: Range) -> &mut [T] { debug_assert!(range.end <= CAP); unsafe { let ptr = self.buf.as_mut_ptr() as *mut T; slice::from_raw_parts_mut(ptr.add(range.start), range.end - range.start) } } } ================================================ FILE: src/util/stack.rs ================================================ use core::mem::size_of; use super::align_down; pub struct Stack<'a> { ptr: &'a mut usize, } impl<'a> Stack<'a> { pub fn new(ptr: &'a mut usize) -> Self { Self { ptr } } pub fn skip_by(&mut self, by: usize) { *self.ptr -= by; } pub unsafe fn offset<'b, T: Sized>(&mut self) -> &'b mut T { self.skip_by(size_of::()); unsafe { &mut *(*self.ptr as *mut T) } } pub fn top(&self) -> usize { *self.ptr } pub unsafe fn push_bytes(&mut self, bytes: &[u8]) { self.skip_by(bytes.len()); unsafe { (*self.ptr as *mut u8).copy_from(bytes.as_ptr(), bytes.len()) }; } pub unsafe fn push(&mut self, value: T) { self.skip_by(size_of::()); unsafe { (*self.ptr as *mut T).write(value) }; } pub fn pop_by(&mut self, by: usize) { *self.ptr += by; } pub unsafe fn pop_bytes(&mut self, len: usize) -> &[u8] { let x = unsafe { core::slice::from_raw_parts(*self.ptr as *const u8, len) }; self.pop_by(len); x } pub unsafe fn pop<'b, T: Sized>(&mut self) -> &'b mut T { let x = unsafe { &mut *(*self.ptr as *mut T) }; self.pop_by(size_of::()); x } pub fn align_down(&mut self, align: usize) { *self.ptr = align_down(*self.ptr, align) } } ================================================ FILE: src/vga_text.rs ================================================ use core::ops::Add; use lazy_static::lazy_static; #[allow(dead_code)] #[derive(Debug, Clone, Copy, PartialEq, Eq)] #[repr(u8)] pub enum Color { Black = 0, Blue = 1, Green = 2, Cyan = 3, Red = 4, Magenta = 5, Brown = 6, LightGray = 7, DarkGray = 8, LightBlue = 9, LightGreen = 10, LightCyan = 11, LightRed = 12, Pink = 13, Yellow = 14, White = 15, } #[derive(Debug, Clone, Copy, PartialEq, Eq)] #[repr(transparent)] pub struct ColorCode(u8); impl ColorCode { pub fn new(foreground: Color, background: Color) -> ColorCode { ColorCode(((background as u8) << 4) | (foreground as u8)) } pub fn background(self) -> Color { unsafe { core::mem::transmute((self.0 >> 4) & 0xf) } } pub fn foreground(self) -> Color { unsafe { core::mem::transmute(self.0 & 0xf) } } } #[derive(Debug, Clone, Copy, PartialEq, Eq)] #[repr(C)] struct ScreenChar { ascii_character: u8, color_code: ColorCode, } pub const VGA_BUFFER_START_PADDR: usize = 0xb8000; pub const BUFFER_HEIGHT: usize = 25; pub const BUFFER_WIDTH: usize = 80; #[repr(transparent)] struct Buffer { chars: [[volatile::Volatile; BUFFER_WIDTH]; BUFFER_HEIGHT], } pub struct Writer { x: usize, y: usize, color_code: ColorCode, buffer: &'static mut Buffer, } impl Writer { pub fn write_byte(&mut self, byte: u8) { match byte { 0x8 => self.backspace(), b'\n' => self.new_line(), b'\r' => self.x = 0, byte => { if self.x >= BUFFER_WIDTH - 1 { self.new_line(); } let row = self.y; let col = self.x; let color_code = self.color_code; self.buffer.chars[row][col].write(ScreenChar { ascii_character: byte, color_code, }); self.move_right(); } } self.cursor_color_hook(); } fn cursor_color_hook(&mut self) { // let cursor = self.buffer.chars[self.y][self.x].read(); // for y in 0..BUFFER_HEIGHT { // for x in 0..BUFFER_WIDTH { // let chr = self.buffer.chars[y][x].read(); // if y == self.y && x == self.x { // self.buffer.chars[y][x].write(ScreenChar { ascii_character: cursor.ascii_character, color_code: ColorCode::new(Color::White, Color::Cyan) }); // } else { // self.buffer.chars[y][x].write(ScreenChar { ascii_character: chr.ascii_character, color_code: self.color_code }); // } // } // } } pub fn backspace(&mut self) { let row = self.y; let col = self.x.saturating_sub(1); let color_code = self.color_code; self.buffer.chars[row][col].write(ScreenChar { ascii_character: b' ', color_code, }); self.x = col; self.cursor_color_hook(); } pub fn write_string(&mut self, s: &str) { for byte in s.bytes() { // match byte { // 0x20..=0x7e | b'\n' | b'\r' | 0x8 => self.write_byte(byte), // // _ => self.write_byte(0xfe), // _ => {}, // } self.write_byte(byte) } } fn new_line(&mut self) { if self.y >= BUFFER_HEIGHT - 1 { for row in 1..BUFFER_HEIGHT { for col in 0..BUFFER_WIDTH { let character = self.buffer.chars[row][col].read(); self.buffer.chars[row - 1][col].write(character); } } self.y = BUFFER_HEIGHT - 1; self.clear_row(self.y); self.x = 0; } else { self.y += 1; self.x = 0; } self.cursor_color_hook(); } fn clear_row(&mut self, row: usize) { let blank = ScreenChar { ascii_character: b' ', color_code: self.color_code, }; for col in 0..BUFFER_WIDTH { self.buffer.chars[row][col].write(blank); } self.cursor_color_hook(); } fn clear_until_end(&mut self) { let blank = ScreenChar { ascii_character: b' ', color_code: self.color_code, }; for col in self.x..BUFFER_WIDTH { self.buffer.chars[self.y][col].write(blank); } for row in self.y + 1..BUFFER_HEIGHT { self.clear_row(row); } self.cursor_color_hook(); } fn clear_until_beginning(&mut self) { let blank = ScreenChar { ascii_character: b' ', color_code: self.color_code, }; for col in 0..self.x { self.buffer.chars[self.y][col].write(blank); } for row in 0..self.y - 1 { self.clear_row(row); } self.cursor_color_hook(); } fn clear_until_eol(&mut self) { let blank = ScreenChar { ascii_character: b' ', color_code: self.color_code, }; for col in self.x..BUFFER_WIDTH { self.buffer.chars[self.y][col].write(blank); } self.cursor_color_hook(); } fn clear_from_bol(&mut self) { let blank = ScreenChar { ascii_character: b' ', color_code: self.color_code, }; for col in 0..self.x { self.buffer.chars[self.y][col].write(blank); } self.cursor_color_hook(); } fn clear_line(&mut self) { self.clear_row(self.y); } fn clear_all(&mut self) { for row in 0..BUFFER_HEIGHT { self.clear_row(row) } self.cursor_color_hook(); } fn move_up(&mut self) { let new_y = self.y.saturating_sub(1); let mut new_x = self.x; while new_x > 0 && self.buffer.chars[new_y][new_x].read().ascii_character == b' ' { new_x -= 1; } self.y = new_y; self.x = new_x; self.cursor_color_hook(); } fn move_down(&mut self) { let new_y = self.y.add(1).min(BUFFER_HEIGHT - 1); let mut new_x = self.x; while new_x > 0 && self.buffer.chars[new_y][new_x].read().ascii_character == b' ' { new_x -= 1; } self.y = new_y; self.x = new_x; self.cursor_color_hook(); } fn move_left(&mut self) { self.x = self.x.saturating_sub(1); self.cursor_color_hook(); } fn move_right(&mut self) { self.x = self.x.add(1).min(BUFFER_WIDTH - 1); self.cursor_color_hook(); } } impl core::fmt::Write for Writer { fn write_str(&mut self, s: &str) -> core::fmt::Result { self.write_string(s); Ok(()) } } lazy_static! { pub static ref WRITER: spin::Mutex = spin::Mutex::new(Writer { y: 0, x: 0, color_code: ColorCode::new(Color::White, Color::Black), buffer: unsafe { &mut *(VGA_BUFFER_START_PADDR as *mut Buffer) }, }); } pub fn clear_screen() { WRITER.lock().clear_all() } pub fn backspace() { WRITER.lock().backspace() } pub fn set_color_code(color_code: ColorCode) { WRITER.lock().color_code = color_code; } pub fn get_color_code() -> ColorCode { WRITER.lock().color_code } pub fn set_cursor_x(x: usize) { WRITER.lock().x = x.min(BUFFER_WIDTH - 1); } pub fn set_cursor_y(y: usize) { WRITER.lock().y = y.min(BUFFER_HEIGHT - 1); } pub fn set_cursor_xy(xy: (usize, usize)) { set_cursor_x(xy.0.min(BUFFER_WIDTH - 1)); set_cursor_y(xy.1.min(BUFFER_HEIGHT - 1)); } pub fn cursor_xy() -> (usize, usize) { let writer = WRITER.lock(); (writer.x, writer.y) } pub fn write_byte(byte: u8) { WRITER.lock().write_byte(byte); } pub fn clear_until_end() { WRITER.lock().clear_until_end(); } pub fn clear_until_beginning() { WRITER.lock().clear_until_beginning(); } pub fn clear_from_bol() { WRITER.lock().clear_from_bol(); } pub fn clear_until_eol() { WRITER.lock().clear_until_eol(); } pub fn clear_line() { WRITER.lock().clear_line(); } pub fn move_up() { WRITER.lock().move_up(); } pub fn move_down() { WRITER.lock().move_down(); } pub fn move_left() { WRITER.lock().move_left(); } pub fn move_right() { WRITER.lock().move_right(); } #[macro_export] macro_rules! vga_print { ($($arg:tt)*) => ($crate::vga_text::_vga_print(format_args!($($arg)*))); } #[macro_export] macro_rules! vga_println { () => ($crate::vga_print!("\n")); ($($arg:tt)*) => ($crate::vga_print!("{}\n", format_args!($($arg)*))); } #[doc(hidden)] pub fn _vga_print(args: core::fmt::Arguments) { use core::fmt::Write; x86_64::instructions::interrupts::without_interrupts(|| { WRITER.lock().write_fmt(args).unwrap(); }); } ================================================ FILE: userland/.gitignore ================================================ /target ================================================ FILE: userland/.vscode/settings.json ================================================ { "rust-analyzer.cargo.target": "x86_64-unknown-linux-musl", } ================================================ FILE: userland/Cargo.toml ================================================ [workspace] members = ["kash", "kados_syscall"] resolver = "3" ================================================ FILE: userland/kados_syscall/.cargo/config.toml ================================================ [build] target = "x86_64-unknown-linux-musl" [target.'cfg(target_os = "linux")'] rustflags = ["-C", "linker=rust-lld", "-C", "relocation-model=static"] ================================================ FILE: userland/kados_syscall/Cargo.toml ================================================ [package] name = "kados_syscall" version = "0.1.0" edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] ================================================ FILE: userland/kados_syscall/src/consts.rs ================================================ #[derive(Debug, Copy, Clone, Eq, PartialEq)] #[repr(i32)] #[allow(unused)] #[allow(clippy::upper_case_acronyms)] pub enum Errno { EPERM = 1, ENOENT = 2, ESRCH = 3, EINTR = 4, EIO = 5, ENXIO = 6, E2BIG = 7, ENOEXEC = 8, EBADF = 9, ECHILD = 10, EAGAIN = 11, ENOMEM = 12, EACCES = 13, EFAULT = 14, ENOTBLK = 15, EBUSY = 16, EEXIST = 17, EXDEV = 18, ENODEV = 19, ENOTDIR = 20, EISDIR = 21, EINVAL = 22, ENFILE = 23, EMFILE = 24, ENOTTY = 25, ETXTBSY = 26, EFBIG = 27, ENOSPC = 28, ESPIPE = 29, EROFS = 30, EMLINK = 31, EPIPE = 32, EDOM = 33, ERANGE = 34, ENOSYS = 38, ELOOP = 40, EADDRINUSE = 98, EADDRNOTAVAIL = 99, ENETDOWN = 100, ENETUNREACH = 101, ENETRESET = 102, ECONNABORTED = 103, ECONNRESET = 104, ENOBUFS = 105, EISCONN = 106, ENOTCONN = 107, /// Temporary errno to use until I get everything POSIX-compatible. ETMP = 255, } pub type SyscallResult = Result; #[inline] pub fn syscall_result(sys_res: isize) -> SyscallResult { if sys_res >= 0 { Ok(sys_res as usize) } else { Err(unsafe { core::mem::transmute((-sys_res) as i32)}) } } pub fn syscall_name_by_number(n: usize) -> &'static str { match n { 0 => "read", 1 => "write", 2 => "open", 3 => "close", 4 => "stat", 5 => "fstat", 6 => "lstat", 7 => "poll", 8 => "lseek", 9 => "mmap", 10 => "mprotect", 11 => "munmap", 12 => "brk", 13 => "rt_sigaction", 14 => "rt_sigprocmask", 15 => "rt_sigreturn", 16 => "ioctl", 17 => "pread64", 18 => "pwrite64", 19 => "readv", 20 => "writev", 21 => "access", 22 => "pipe", 23 => "select", 24 => "sched_yield", 25 => "mremap", 26 => "msync", 27 => "mincore", 28 => "madvise", 29 => "shmget", 30 => "shmat", 31 => "shmctl", 32 => "dup", 33 => "dup2", 34 => "pause", 35 => "nanosleep", 36 => "getitimer", 37 => "alarm", 38 => "setitimer", 39 => "getpid", 40 => "sendfile", 41 => "socket", 42 => "connect", 43 => "accept", 44 => "sendto", 45 => "recvfrom", 46 => "sendmsg", 47 => "recvmsg", 48 => "shutdown", 49 => "bind", 50 => "listen", 51 => "getsockname", 52 => "getpeername", 53 => "socketpair", 54 => "setsockopt", 55 => "getsockopt", 56 => "clone", 57 => "fork", 58 => "vfork", 59 => "execve", 60 => "exit", 61 => "wait4", 62 => "kill", 63 => "uname", 64 => "semget", 65 => "semop", 66 => "semctl", 67 => "shmdt", 68 => "msgget", 69 => "msgsnd", 70 => "msgrcv", 71 => "msgctl", 72 => "fcntl", 73 => "flock", 74 => "fsync", 75 => "fdatasync", 76 => "truncate", 77 => "ftruncate", 78 => "getdents", 79 => "getcwd", 80 => "chdir", 81 => "fchdir", 82 => "rename", 83 => "mkdir", 84 => "rmdir", 85 => "creat", 86 => "link", 87 => "unlink", 88 => "symlink", 89 => "readlink", 90 => "chmod", 91 => "fchmod", 92 => "chown", 93 => "fchown", 94 => "lchown", 95 => "umask", 96 => "gettimeofday", 97 => "getrlimit", 98 => "getrusage", 99 => "sysinfo", 100 => "times", 101 => "ptrace", 102 => "getuid", 103 => "syslog", 104 => "getgid", 105 => "setuid", 106 => "setgid", 107 => "geteuid", 108 => "getegid", 109 => "setpgid", 110 => "getppid", 111 => "getpgrp", 112 => "setsid", 113 => "setreuid", 114 => "setregid", 115 => "getgroups", 116 => "setgroups", 117 => "setresuid", 118 => "getresuid", 119 => "setresgid", 120 => "getresgid", 121 => "getpgid", 122 => "setfsuid", 123 => "setfsgid", 124 => "getsid", 125 => "capget", 126 => "capset", 127 => "rt_sigpending", 128 => "rt_sigtimedwait", 129 => "rt_sigqueueinfo", 130 => "rt_sigsuspend", 131 => "sigaltstack", 132 => "utime", 133 => "mknod", 134 => "uselib", 135 => "personality", 136 => "ustat", 137 => "statfs", 138 => "fstatfs", 139 => "sysfs", 140 => "getpriority", 141 => "setpriority", 142 => "sched_setparam", 143 => "sched_getparam", 144 => "sched_setscheduler", 145 => "sched_getscheduler", 146 => "sched_get_priority_max", 147 => "sched_get_priority_min", 148 => "sched_rr_get_interval", 149 => "mlock", 150 => "munlock", 151 => "mlockall", 152 => "munlockall", 153 => "vhangup", 154 => "modify_ldt", 155 => "pivot_root", 156 => "_sysctl", 157 => "prctl", 158 => "arch_prctl", 159 => "adjtimex", 160 => "setrlimit", 161 => "chroot", 162 => "sync", 163 => "acct", 164 => "settimeofday", 165 => "mount", 166 => "umount2", 167 => "swapon", 168 => "swapoff", 169 => "reboot", 170 => "sethostname", 171 => "setdomainname", 172 => "iopl", 173 => "ioperm", 174 => "create_module", 175 => "init_module", 176 => "delete_module", 177 => "get_kernel_syms", 178 => "query_module", 179 => "quotactl", 180 => "nfsservctl", 181 => "getpmsg", 182 => "putpmsg", 183 => "afs_syscall", 184 => "tuxcall", 185 => "security", 186 => "gettid", 187 => "readahead", 188 => "setxattr", 189 => "lsetxattr", 190 => "fsetxattr", 191 => "getxattr", 192 => "lgetxattr", 193 => "fgetxattr", 194 => "listxattr", 195 => "llistxattr", 196 => "flistxattr", 197 => "removexattr", 198 => "lremovexattr", 199 => "fremovexattr", 200 => "tkill", 201 => "time", 202 => "futex", 203 => "sched_setaffinity", 204 => "sched_getaffinity", 205 => "set_thread_area", 206 => "io_setup", 207 => "io_destroy", 208 => "io_getevents", 209 => "io_submit", 210 => "io_cancel", 211 => "get_thread_area", 212 => "lookup_dcookie", 213 => "epoll_create", 214 => "epoll_ctl_old", 215 => "epoll_wait_old", 216 => "remap_file_pages", 217 => "getdents64", 218 => "set_tid_address", 219 => "restart_syscall", 220 => "semtimedop", 221 => "fadvise64", 222 => "timer_create", 223 => "timer_settime", 224 => "timer_gettime", 225 => "timer_getoverrun", 226 => "timer_delete", 227 => "clock_settime", 228 => "clock_gettime", 229 => "clock_getres", 230 => "clock_nanosleep", 231 => "exit_group", 232 => "epoll_wait", 233 => "epoll_ctl", 234 => "tgkill", 235 => "utimes", 236 => "vserver", 237 => "mbind", 238 => "set_mempolicy", 239 => "get_mempolicy", 240 => "mq_open", 241 => "mq_unlink", 242 => "mq_timedsend", 243 => "mq_timedreceive", 244 => "mq_notify", 245 => "mq_getsetattr", 246 => "kexec_load", 247 => "waitid", 248 => "add_key", 249 => "request_key", 250 => "keyctl", 251 => "ioprio_set", 252 => "ioprio_get", 253 => "inotify_init", 254 => "inotify_add_watch", 255 => "inotify_rm_watch", 256 => "migrate_pages", 257 => "openat", 258 => "mkdirat", 259 => "mknodat", 260 => "fchownat", 261 => "futimesat", 262 => "newfstatat", 263 => "unlinkat", 264 => "renameat", 265 => "linkat", 266 => "symlinkat", 267 => "readlinkat", 268 => "fchmodat", 269 => "faccessat", 270 => "pselect6", 271 => "ppoll", 272 => "unshare", 273 => "set_robust_list", 274 => "get_robust_list", 275 => "splice", 276 => "tee", 277 => "sync_file_range", 278 => "vmsplice", 279 => "move_pages", 280 => "utimensat", 281 => "epoll_pwait", 282 => "signalfd", 283 => "timerfd_create", 284 => "eventfd", 285 => "fallocate", 286 => "timerfd_settime", 287 => "timerfd_gettime", 288 => "accept4", 289 => "signalfd4", 290 => "eventfd2", 291 => "epoll_create1", 292 => "dup3", 293 => "pipe2", 294 => "inotify_init1", 295 => "preadv", 296 => "pwritev", 297 => "rt_tgsigqueueinfo", 298 => "perf_event_open", 299 => "recvmmsg", 300 => "fanotify_init", 301 => "fanotify_mark", 302 => "prlimit64", 303 => "name_to_handle_at", 304 => "open_by_handle_at", 305 => "clock_adjtime", 306 => "syncfs", 307 => "sendmmsg", 308 => "setns", 309 => "getcpu", 310 => "process_vm_readv", 311 => "process_vm_writev", 312 => "kcmp", 313 => "finit_module", 314 => "sched_setattr", 315 => "sched_getattr", 316 => "renameat2", 317 => "seccomp", 318 => "getrandom", 319 => "memfd_create", 320 => "kexec_file_load", 321 => "bpf", 322 => "execveat", 323 => "userfaultfd", 324 => "membarrier", 325 => "mlock2", 326 => "copy_file_range", 327 => "preadv2", 328 => "pwritev2", 329 => "pkey_mprotect", 330 => "pkey_alloc", 331 => "pkey_free", 332 => "statx", 333 => "io_pgetevents", 334 => "rseq", 51729 => "kados_debug", _ => "(unknown)", } } pub const SYS_READ: usize = 0; pub const SYS_WRITE: usize = 1; pub const SYS_OPEN: usize = 2; pub const SYS_CLOSE: usize = 3; pub const SYS_STAT: usize = 4; pub const SYS_FSTAT: usize = 5; pub const SYS_LSTAT: usize = 6; pub const SYS_POLL: usize = 7; pub const SYS_MMAP: usize = 9; pub const SYS_MPROTECT: usize = 10; pub const SYS_MUNMAP: usize = 11; pub const SYS_BRK: usize = 12; pub const SYS_RT_SIGACTION: usize = 13; pub const SYS_RT_SIGPROCMASK: usize = 14; pub const SYS_RT_SIGRETURN: usize = 15; pub const SYS_IOCTL: usize = 16; pub const SYS_WRITEV: usize = 20; pub const SYS_PIPE: usize = 22; pub const SYS_SELECT: usize = 23; pub const SYS_DUP2: usize = 33; pub const SYS_GETPID: usize = 39; pub const SYS_SOCKET: usize = 41; pub const SYS_CONNECT: usize = 42; pub const SYS_ACCEPT: usize = 43; pub const SYS_SENDTO: usize = 44; pub const SYS_RECVFROM: usize = 45; pub const SYS_SHUTDOWN: usize = 48; pub const SYS_BIND: usize = 49; pub const SYS_LISTEN: usize = 50; pub const SYS_GETSOCKNAME: usize = 51; pub const SYS_GETPEERNAME: usize = 52; pub const SYS_GETSOCKOPT: usize = 55; pub const SYS_CLONE: usize = 56; pub const SYS_FORK: usize = 57; pub const SYS_EXECVE: usize = 59; pub const SYS_EXIT: usize = 60; pub const SYS_WAIT4: usize = 61; pub const SYS_KILL: usize = 62; pub const SYS_UNAME: usize = 63; pub const SYS_FCNTL: usize = 72; pub const SYS_FSYNC: usize = 74; pub const SYS_GETCWD: usize = 79; pub const SYS_CHDIR: usize = 80; pub const SYS_MKDIR: usize = 83; pub const SYS_LINK: usize = 86; pub const SYS_READLINK: usize = 89; pub const SYS_CHMOD: usize = 90; pub const SYS_CHOWN: usize = 92; pub const SYS_GETUID: usize = 102; pub const SYS_SYSLOG: usize = 103; pub const SYS_SETUID: usize = 105; pub const SYS_SETGID: usize = 106; pub const SYS_GETEUID: usize = 107; pub const SYS_SETPGID: usize = 109; pub const SYS_GETPPID: usize = 110; pub const SYS_GETPGRP: usize = 111; pub const SYS_GETPGID: usize = 121; pub const SYS_SETGROUPS: usize = 116; pub const SYS_ARCH_PRCTL: usize = 158; pub const SYS_REBOOT: usize = 169; pub const SYS_GETTID: usize = 186; pub const SYS_GETDENTS64: usize = 217; pub const SYS_SET_TID_ADDRESS: usize = 218; pub const SYS_CLOCK_GETTIME: usize = 228; pub const SYS_EXIT_GROUP: usize = 231; pub const SYS_UTIMES: usize = 235; pub const SYS_LINKAT: usize = 265; pub const SYS_GETRANDOM: usize = 318; ================================================ FILE: userland/kados_syscall/src/lib.rs ================================================ use core::arch::asm; use consts::*; pub mod consts; #[no_mangle] #[inline(always)] #[allow(clippy::missing_safety_doc)] pub extern "C" fn syscall0(n: usize) -> isize { let ret: isize; unsafe { asm!("syscall", in("rax") n, lateout("rax") ret); } ret } #[no_mangle] #[inline(always)] #[allow(clippy::missing_safety_doc)] pub extern "C" fn syscall1(n: usize, a1: usize) -> isize { let ret: isize; unsafe { asm!("syscall", in("rax") n, in("rdi") a1, lateout("rax") ret); } ret } #[no_mangle] #[inline(always)] #[allow(clippy::missing_safety_doc)] pub extern "C" fn syscall2(n: usize, a1: usize, a2: usize) -> isize { let ret: isize; unsafe { asm!("syscall", in("rax") n, in("rdi") a1, in("rsi") a2, lateout("rax") ret); } ret } #[no_mangle] #[inline(always)] #[allow(clippy::missing_safety_doc)] pub extern "C" fn syscall3(n: usize, a1: usize, a2: usize, a3: usize) -> isize { let ret: isize; unsafe { asm!("syscall", in("rax") n, in("rdi") a1, in("rsi") a2, in("rdx") a3, lateout("rax") ret); } ret } #[no_mangle] #[inline(always)] #[allow(clippy::missing_safety_doc)] pub extern "C" fn syscall4(n: usize, a1: usize, a2: usize, a3: usize, a4: usize) -> isize { let ret: isize; unsafe { asm!("syscall", in("rax") n, in("rdi") a1, in("rsi") a2, in("rdx") a3, in("r10") a4, lateout("rax") ret); } ret } #[no_mangle] #[inline(always)] #[allow(clippy::missing_safety_doc)] pub extern "C" fn syscall5( n: usize, a1: usize, a2: usize, a3: usize, a4: usize, a5: usize, ) -> isize { let ret: isize; unsafe { asm!("syscall", in("rax") n, in("rdi") a1, in("rsi") a2, in("rdx") a3, in("r10") a4, in("r8") a5, lateout("rax") ret); } ret } #[no_mangle] #[inline(always)] #[allow(clippy::missing_safety_doc)] pub extern "C" fn syscall6( n: usize, a1: usize, a2: usize, a3: usize, a4: usize, a5: usize, a6: usize, ) -> isize { let ret: isize; unsafe { asm!("syscall", in("rax") n, in("rdi") a1, in("rsi") a2, in("rdx") a3, in("r10") a4, in("r8") a5, in("r9") a6, lateout("rax") ret); } ret } pub fn sys_arch_prctl(code: i32, address: usize) -> SyscallResult { syscall_result(syscall2(SYS_ARCH_PRCTL, code as usize, address)) } pub fn sys_set_tid_address(address: usize) -> SyscallResult { syscall_result(syscall1(SYS_SET_TID_ADDRESS, address)) } pub fn sys_write(fd: i32, address: usize, len: usize) -> SyscallResult { syscall_result(syscall3(SYS_WRITE, fd as usize, address, len)) } pub fn sys_writev(fd: i32, iov_base: usize, iov_count: usize) -> SyscallResult { syscall_result(syscall3(SYS_WRITEV, fd as usize, iov_base, iov_count)) } pub fn sys_read(fd: i32, address: usize, len: usize) -> SyscallResult { syscall_result(syscall3(SYS_READ, fd as usize, address, len)) } pub fn sys_fork() -> SyscallResult { syscall_result(syscall0(SYS_FORK)) } pub fn sys_wait4(pid: i32, status_addr: usize, options: i32, rusage_addr: usize) -> SyscallResult { syscall_result(syscall4( SYS_WAIT4, pid as usize, status_addr, options as usize, rusage_addr, )) } pub fn sys_execve(path_addr: usize, argv_addr: usize, envp_addr: usize) -> SyscallResult { syscall_result(syscall3(SYS_EXECVE, path_addr, argv_addr, envp_addr)) } pub fn sys_getcwd(buf_addr: usize, buf_len: usize) -> SyscallResult { syscall_result(syscall2(SYS_GETCWD, buf_addr, buf_len)) } ================================================ FILE: userland/rust-toolchain.toml ================================================ [toolchain] channel = "nightly" targets = ["x86_64-unknown-linux-musl"] components = ["rust-src"]