Compare commits

...

112 Commits

Author SHA1 Message Date
eeb9e16a74
libos: Make os::File a wrapper around stdio's FILE
All checks were successful
continuous-integration/drone/push Build is passing
This way, os::File benefits from the same buffering as stdio.h does.
2023-07-22 12:40:02 +02:00
358493a7bc
kernel: Add a system for release names in uname(), call alpha releases "Mercury"
All checks were successful
continuous-integration/drone/push Build is passing
2023-07-22 12:21:01 +02:00
5110d740b8
all: Update the version number to 0.4.0
All checks were successful
continuous-integration/drone/push Build is passing
2023-07-22 12:11:02 +02:00
759fb4fe0e
tools: Make replace-stdint.sh replace types only if they're followed by a space
All checks were successful
continuous-integration/drone/push Build is passing
Otherwise, size_to_read -> usizeo_read.
2023-07-22 11:59:41 +02:00
098109f16b
tools: Make sure formatting scripts cover all sources 2023-07-22 11:59:02 +02:00
9ef09cfc88
libc+libluna: Add case-insensitive string comparison functions 2023-07-22 11:58:28 +02:00
c17e1a5802
Update README.md
All checks were successful
continuous-integration/drone/push Build is passing
2023-07-22 11:45:35 +02:00
085d2895e8
libc: Implement setbuf(), setbuffer(), and setlinebuf()
All checks were successful
continuous-integration/drone/pr Build is passing
continuous-integration/drone/push Build is passing
These are all simple wrappers around setvbuf().
2023-07-22 11:36:20 +02:00
77022abafd
libc: Implement ungetc
All checks were successful
continuous-integration/drone/pr Build is passing
2023-07-22 11:25:20 +02:00
19b4aa9f81
libc: Flush buffers before dealing with file positions 2023-07-22 11:19:48 +02:00
420270ebd4
libc: Implement read buffering =D 2023-07-22 11:17:51 +02:00
d60ad184f1
libc: A bit of nice refactoring 2023-07-22 10:58:34 +02:00
a3ed950be8
libc: Basic write buffers
All checks were successful
continuous-integration/drone/pr Build is passing
2023-07-22 00:04:27 +02:00
cfb0ead2d9
libc: Flush all open streams on exit
This does nothing for now, but prepares for buffering.
2023-07-21 22:56:03 +02:00
5458286309
libos: Add Process::exit()
All checks were successful
continuous-integration/drone/push Build is passing
2023-07-21 21:21:08 +02:00
c72c6312d4
sh: Use input_file instead of hardcoding stdin everywhere in tcsetpgrp()
All checks were successful
continuous-integration/drone/push Build is passing
2023-07-21 21:08:27 +02:00
7a4d3ba495
sh: Add a few more shell builtins
All checks were successful
continuous-integration/drone/push Build is passing
2023-07-21 21:06:10 +02:00
16b385fc7b
libluna: Some fixes so that HashTable collisions work properly 2023-07-21 21:04:25 +02:00
4439ef8ec6
sh: Add a system to easily add flexible shell builtins
All checks were successful
continuous-integration/drone/push Build is passing
2023-07-21 20:44:01 +02:00
ff9e01641e
apps: Add a Game of Life implementation
All checks were successful
continuous-integration/drone/push Build is passing
2023-07-21 15:51:21 +02:00
36a74fd8d6
kernel/x86_64: Provide an alternate kernel stack for exceptions
All checks were successful
continuous-integration/drone/push Build is passing
This avoids stack-related triple faults, hopefully.

Closes #33.
2023-07-21 15:14:52 +02:00
310b325af8
kernel: Avoid some more -Wconversion errors in TextConsole 2023-07-21 15:14:05 +02:00
0d924f89d3
tools: Avoid making the tests interfere with the main build directory
All checks were successful
continuous-integration/drone/push Build is passing
2023-07-21 14:49:59 +02:00
de7e58c274
StringView: Fix equality operator
All checks were successful
continuous-integration/drone/push Build is passing
2023-07-21 14:26:54 +02:00
c24a261233
tools: Do not use KVM when it's not supported
All checks were successful
continuous-integration/drone/push Build is passing
2023-07-21 14:17:14 +02:00
edeb420d0d
Update drone.yml
Some checks failed
continuous-integration/drone/push Build is failing
2023-07-21 14:11:44 +02:00
cd6bf745a7
tests+kernel+init: Run tests automatically in a headless way
Some checks failed
continuous-integration/drone/push Build is failing
2023-07-21 14:09:37 +02:00
bcfee628cb
kernel: Remove outdated FIXME
All checks were successful
continuous-integration/drone/push Build is passing
2023-07-15 13:19:47 +02:00
0d41e1f7b6
kernel/ext2: Change outdated FIXME
All checks were successful
continuous-integration/drone/push Build is passing
2023-07-15 13:17:58 +02:00
f9003d7a58
kernel: Mask away unsafe bits in rflags when restoring state after a signal
All checks were successful
continuous-integration/drone/push Build is passing
2023-07-15 11:54:48 +02:00
89786d8be2
kernel: Save/restore the SSE/FPU state when executing signal handlers 2023-07-15 11:53:50 +02:00
de6fe7f7c2
kernel+libc+sh: Make the TTY device actually follow termios rules
All checks were successful
continuous-integration/drone/push Build is passing
Like, so much more termios compatibility!
2023-07-13 20:33:20 +02:00
efd5bae7a5
kernel: Implement querying the terminal window size
Some checks failed
continuous-integration/drone/push Build is failing
2023-07-12 22:09:28 +02:00
78ea5dc352
base: Rename selene-home.sh to mount-home.sh
All checks were successful
continuous-integration/drone/push Build is passing
This makes its purpose clearer.
2023-07-12 19:47:45 +02:00
6c3ab3b27d
init+base: Allow 'Description' fields in service files 2023-07-12 19:46:53 +02:00
95cce6d592
base: Rename /etc/init/00-selene to 00-home
All checks were successful
continuous-integration/drone/push Build is passing
This makes its purpose a little bit clearer.
2023-07-12 19:40:42 +02:00
192621eac5
base: Mount the user's home directory before showing the MOTD
All checks were successful
continuous-integration/drone/push Build is passing
This way, the MOTD is shown just before login and there's less noticeable delay between the two.
2023-07-12 19:39:41 +02:00
acf4fef6f5
sysfuzz: Skip invoking sigreturn
All checks were successful
continuous-integration/drone/push Build is passing
2023-07-12 19:37:22 +02:00
546d900454
libc+apps: Start implementing POSIX-compliant termios.h wrappers around tty ioctls
All checks were successful
continuous-integration/drone/push Build is passing
2023-07-12 19:23:06 +02:00
2951d6d112
libc+tests: Check for manual modifications of environ
All checks were successful
continuous-integration/drone/push Build is passing
Closes #31.
2023-07-12 16:25:06 +02:00
5f698b4774
kernel: Don't create a new kernel stack on exec()
All checks were successful
continuous-integration/drone/push Build is passing
The old one was not getting freed, creating a memory leak every exec(),
which can get huge over time.
Plus, there was no need for a new stack.
And we couldn't just free the old one, since sys_execve() runs on the old stack...
2023-07-12 16:06:56 +02:00
f629e17ff4
kernel/x86_64: Only show kernel addresses in backtraces
This avoids walking off into userspace memory where we don't know what could happen.
2023-07-12 16:04:45 +02:00
1f6a0db188
su: Handle more signals gracefully
All checks were successful
continuous-integration/drone/push Build is passing
2023-07-12 13:52:49 +02:00
81e1fdf81e
kernel+libc+login+sh+su: Implement foreground and background process groups in the default console
All checks were successful
continuous-integration/drone/push Build is passing
Also, the console sends SIGINT to the foreground process group when ^C is pressed!
2023-07-12 13:49:37 +02:00
9f45026cc2
kernel+sh: Implement interruptible syscalls 2023-07-12 13:48:43 +02:00
71ff763dd9
kernel+libc: Add the SIGTTIN and SIGTTOU signals 2023-07-12 13:45:36 +02:00
b64093dee5
kernel+libc: Implement getpgid() 2023-07-12 13:44:25 +02:00
d27ffce5db
kernel: Move the signal handling logic to after a syscall sets its return value
When a signal was caught after a syscall, it was doing so without preserving the return value of the syscall.
2023-07-12 13:34:30 +02:00
1091798195
libc: Add stub memory.h header for legacy programs
All checks were successful
continuous-integration/drone/push Build is passing
2023-07-12 11:24:54 +02:00
9cf35f761f
libc: Fix another conversion error
All checks were successful
continuous-integration/drone/push Build is passing
2023-07-11 12:45:55 +02:00
a51fb6a428
libc: Fix conversion error in isatty()
Some checks failed
continuous-integration/drone/push Build is failing
2023-07-11 12:45:08 +02:00
69f9701097
kernel+libc: Implement isatty()
Some checks failed
continuous-integration/drone/push Build is failing
2023-07-11 12:05:09 +02:00
7328cfe734
kernel: Add basic process groups
All checks were successful
continuous-integration/drone/push Build is passing
2023-07-11 11:51:07 +02:00
6546f490b6
Update README.md 2023-07-11 11:50:57 +02:00
f7e8fd9cb8
libc: Add octal specifiers to inttypes.h 2023-07-11 11:49:27 +02:00
0fed45d1c6
libc: Implement _exit()
Apparently, I implemented _Exit in stdlib.h but forgot to add _exit to unistd.h...
2023-07-11 11:49:10 +02:00
82411789e8
libos+apps: Add kill
All checks were successful
continuous-integration/drone/push Build is passing
2023-07-10 22:17:05 +02:00
86d14e0d0e
kernel+libc: Add the SA_NODEFER and SA_RESETHAND flags for sigaction()
All checks were successful
continuous-integration/drone/pr Build is passing
continuous-integration/drone/push Build is passing
2023-07-10 21:54:04 +02:00
237184a8bf
libc+sh: Implement strsignal and use it in the shell
All checks were successful
continuous-integration/drone/pr Build is passing
2023-07-10 21:39:22 +02:00
e0b5acb2ab
libc: Make struct sigaction C-compatible
All checks were successful
continuous-integration/drone/pr Build is passing
2023-07-10 21:19:43 +02:00
66365e15a7
libc: Block and ignore appropriate signals in system()
All checks were successful
continuous-integration/drone/pr Build is passing
2023-07-10 21:17:56 +02:00
4a5947e10e
libc: Implement signal() 2023-07-10 21:17:25 +02:00
fe9827bbeb
kernel: Fix fallthrough in switch statement
All checks were successful
continuous-integration/drone/pr Build is passing
2023-07-10 21:09:12 +02:00
3df40beaf2
libc: Rewrite abort() using the new signals
Some checks failed
continuous-integration/drone/pr Build is failing
2023-07-10 21:08:23 +02:00
8066e8f1d8
kernel+libc: Implement sigprocmask() and friends
Some checks failed
continuous-integration/drone/pr Build is failing
2023-07-10 21:01:59 +02:00
015419b8f5
kernel: Generate signals when children exit / when faults occur
Userspace can now catch segfaults!
2023-07-10 20:49:22 +02:00
60d68b74e1
kernel: Define a good set of default signals
Most of these have POSIX-defined numbers.
2023-07-10 20:30:37 +02:00
cde467ee46
kernel: Support returning termination signals from waitpid
All checks were successful
continuous-integration/drone/pr Build is passing
2023-07-10 20:16:06 +02:00
fc3fdc2b87
kernel: Add default actions for signals 2023-07-10 19:59:01 +02:00
bdcb690a7a
kernel: Avoid processing unregistered signals for init 2023-07-10 19:48:46 +02:00
15d6aae701
kernel+libc: Implement basic signals 2023-07-10 19:46:57 +02:00
15199a2366
libluna+libc: Implement memchr() and strstr()
All checks were successful
continuous-integration/drone/push Build is passing
2023-07-10 15:30:05 +02:00
56f3d26969
kernel+libluna: Fix the CRC32 algorithm and use it to verify the GPT header
All checks were successful
continuous-integration/drone/push Build is passing
2023-07-10 14:54:55 +02:00
16b0531d42
kernel+apps+base+tools: Use Ext2 for the root partition file system
All checks were successful
continuous-integration/drone/push Build is passing
init is now split into two parts: preinit, which lives in the initrd and prepares the root file system for init,
and the actual /usr/bin/init, which lives in the root partition and starts services and reaps zombies.

The kernel now looks for /bin/preinit instead of /bin/init as the executable for the init process.

All configuration files in initrd/etc have been moved to base/etc. (The plan is to have only moon and preinit in the initrd.)

Since the current Ext2 implementation is read-only (and it's on a CDROM so it would be read-only anyways),
/home/selene is a tmpfs (as well as /tmp), to allow for a writable home directory.

The system is slower now, but that's to expect since the Ext2 code doesn't use caching and the ATA code still uses PIO.
2023-07-10 13:05:06 +02:00
40413eee18
kernel: Panic when PID 1 exits/crashes 2023-07-10 13:04:47 +02:00
e3552d9df0
kernel: Log hostname changes 2023-07-10 13:04:40 +02:00
a1b92fcc3f
kernel: Add the MOUNT_DEBUG flag 2023-07-10 13:04:34 +02:00
0b488c1232
kernel: Actually use config.cmake
Looks like file(EXISTS) needs a full path.
2023-07-10 13:04:26 +02:00
b920ffee42
kernel: Don't use an active directory for the idle thread
Just take the currently used directory.
2023-07-10 13:04:18 +02:00
503dc72686
kernel: Set kernel threads' initial active directories to avoid taking the first directory they use
This ends up being init's directory, which is fine when init's directory doesn't change...
but a little less fine when the init process calls exec()...
which is what it does in the new configuration I'm testing...
2023-07-10 13:04:00 +02:00
7908c5a63e
kernel/ATA: Do not discard the controller if at least one channel initialized properly
This was causing problems in VirtualBox.
2023-07-10 13:01:43 +02:00
ae0cd155c3
kernel: Fix AddressSpace's move assignment operator
All checks were successful
continuous-integration/drone/push Build is passing
2023-07-09 20:43:03 +02:00
c599251d2a
kernel: Rename UserVM to AddressSpace
All checks were successful
continuous-integration/drone/push Build is passing
This is a more appropriate name now that it does more stuff than allocate virtual memory.

To be fair, the name was a bit awkward anyway. Should have been UserVMAllocator I guess.
2023-07-09 20:38:04 +02:00
5e564e9ae3
kernel: Move Thread::self_directory to UserVM
Since this is only used by user threads, UserVM is a convenient/related place to store the PageDirectory in a RAII manner.
2023-07-09 20:32:42 +02:00
5c9503ac71
libos+cp: Add prompt functionality
All checks were successful
continuous-integration/drone/push Build is passing
2023-07-08 18:30:39 +02:00
6d9ba8deb4
libos+apps: Make the vector argument more flexible
All checks were successful
continuous-integration/drone/push Build is passing
2023-07-08 16:44:02 +02:00
af26dce038
kernel: Zero physical frames instead of virtual pages
All checks were successful
continuous-integration/drone/push Build is passing
The memset() happens a bit earlier, and we don't have to remap the
mapped virtual memory.
2023-07-04 00:49:26 +02:00
3b1219ecf2
kernel/ext2: Make the inner extended superblock struct packed as well
All checks were successful
continuous-integration/drone/push Build is passing
2023-07-02 19:50:27 +02:00
bd757d204e
kernel+libos+libluna: Avoid copying and reallocation when creating Strings
All checks were successful
continuous-integration/drone/push Build is passing
This is done by reusing the existing buffers in Vector and Buffer instances.
2023-07-02 19:30:25 +02:00
498e20561f
libluna: Add release_data() overloads to Buffer and Vector
This can be used to transfer the underlying data to a String object without copying.
2023-07-02 19:29:04 +02:00
d363d5e915
kernel/ext2: Make sure we don't crash when accessing the last inode
All checks were successful
continuous-integration/drone/push Build is passing
2023-07-02 17:30:14 +02:00
f0a7098470
apps: Add cp
All checks were successful
continuous-integration/drone/push Build is passing
2023-07-02 16:38:24 +02:00
6db0a2649c
libluna: Add a variant of PathParser::join() for relative paths 2023-07-02 16:38:12 +02:00
dd914b16d1
kernel: Stop showing memory stats at boot
All checks were successful
continuous-integration/drone/push Build is passing
No userspace interface yet, but this can be shown using Shift+Ctrl+M.
2023-07-02 16:02:38 +02:00
f8dc7c62e2
Update README.md
All checks were successful
continuous-integration/drone/push Build is passing
2023-07-01 17:40:44 +02:00
21d093b1fa
initrd: Rename test-ext2fs to mount-ext2fs
All checks were successful
continuous-integration/drone/pr Build is passing
continuous-integration/drone/push Build is passing
2023-06-25 20:54:37 +02:00
e9e7b22323
kernel: Separate a thread's page directory into two
All checks were successful
continuous-integration/drone/pr Build is passing
The self directory, and the active directory. The active directory is the one the thread is currently using,
and the self directory is the one the thread owns.

This lets us keep track of both, which fixes ext2 executables crashing the system.
2023-06-25 20:35:40 +02:00
5f4103251a
kernel: Preserve the new page directory while exec() is running 2023-06-25 20:35:40 +02:00
491416ddaf
tools: Bump up the ext2 filesystem size 2023-06-25 20:35:39 +02:00
da689dd1a7
kernel/ext2: Allow reading up to 4 MB of data from files
This is done by scanning the singly indirect pointer of the inode.
2023-06-25 20:35:39 +02:00
41f578aa18
kernel/ext2: Add support for symbolic links 2023-06-25 20:35:38 +02:00
34e1ef36b1
kernel: Make pivot_root() reset the parent entry of the new root directory
Otherwise it would just be pointing to the old parent fs, and we don't want that.
2023-06-25 20:35:38 +02:00
a62265b504
kernel/ext2: Implement directory traversal 2023-06-25 20:35:37 +02:00
d7486326bf
tools: Generate the Ext2 filesystem using genext2fs instead
mkbootimg's filenames have some kind of bug...
2023-06-25 20:35:37 +02:00
4fe6c506ec
kernel/ext2: Implement Inode::read() 2023-06-25 20:35:37 +02:00
77686b26f8
kernel/Ext2: Read the root inode metadata from the disk 2023-06-25 20:35:36 +02:00
2aa7056e11
mount: Put the source argument first 2023-06-25 20:35:36 +02:00
a9460469d9
kernel+libc+apps: Add a source parameter to the mount() system call 2023-06-25 20:35:35 +02:00
707f64acb5
kernel: Add an Ext2 filesystem skeleton 2023-06-25 20:35:35 +02:00
ddf63471a8
kernel: Add missing debug flag to debug.cmake
All checks were successful
continuous-integration/drone/push Build is passing
2023-06-25 20:35:15 +02:00
3b6f5b28fc
kernel: Make the configurable filename restrictions actually compile
All checks were successful
continuous-integration/drone/push Build is passing
2023-06-22 20:24:33 +02:00
fdf2bb2501
kernel: Make the filename restrictions configurable
All checks were successful
continuous-integration/drone/push Build is passing
2023-06-22 20:22:43 +02:00
167 changed files with 4040 additions and 618 deletions

View File

@ -7,15 +7,15 @@ platform:
os: linux
steps:
- name: build
- name: build-and-test
image: ubuntu
commands:
- apt update
- apt install build-essential cmake ninja-build wget nasm -y
- apt install build-essential cmake ninja-build wget nasm genext2fs qemu-system git -y
- wget https://pub.cloudapio.eu/luna/toolchains/ci-toolchain-arm64.tar.gz --quiet
- tar xf ci-toolchain-arm64.tar.gz
- rm ci-toolchain-arm64.tar.gz
- tools/rebuild-iso.sh
- tools/run-tests.sh
trigger:
branch:

3
.gitignore vendored
View File

@ -4,7 +4,6 @@ build/
initrd/boot/moon
env-local.sh
initrd/bin/**
initrd/tests/**
base/
base/usr/**
.fakeroot
kernel/config.cmake

View File

@ -5,7 +5,8 @@ set(CMAKE_CXX_COMPILER_WORKS 1)
set(CMAKE_CROSSCOMPILING true)
project(Luna LANGUAGES C CXX ASM ASM_NASM VERSION 0.3.0)
project(Luna LANGUAGES C CXX ASM ASM_NASM VERSION 0.4.0)
set(LUNA_RELEASE_NAME "Mercury") # Name for alpha releases
set(LUNA_ROOT ${CMAKE_CURRENT_LIST_DIR})
set(LUNA_BASE ${CMAKE_CURRENT_LIST_DIR}/base)
@ -48,3 +49,4 @@ add_subdirectory(libc)
add_subdirectory(kernel)
add_subdirectory(apps)
add_subdirectory(tests)
add_subdirectory(shell)

View File

@ -6,77 +6,51 @@ A very basic POSIX-based operating system for personal computers, written in C++
## Features
- x86_64-compatible lightweight [kernel](kernel/).
- Preemptive multitasking, with a round-robin [scheduler](kernel/src/thread/) that can switch between tasks.
- [Virtual file system](kernel/src/fs/) with a simple but working [tmpfs](kernel/src/fs/tmpfs/) populated from the initial ramdisk.
- Preemptive multitasking, with a round-robin [scheduler](kernel/src/thread/).
- [Virtual file system](kernel/src/fs/) with a simple [tmpfs](kernel/src/fs/tmpfs/) and read-only [ext2](kernel/src/fs/ext2/) support.
- Can [load ELF programs](kernel/src/thread/ELF.cpp) from the file system as userspace tasks.
- Boots from an [ext2](apps/preinit.cpp) root filesystem (a bit slow for now).
- [System call](kernel/src/sys/) interface and [C Library](libc/), aiming to be mostly POSIX-compatible.
- POSIX [signal](libc/src/signal.cpp) support.
- Designed to be [portable](kernel/src/arch), no need to be restricted to x86_64.
- Fully [UTF-8 aware](libluna/include/luna/Utf8.h), **everywhere**.
- [Thread](libluna/include/luna/Atomic.h) [safety](kernel/src/thread/Spinlock.h) (supposedly).
- Designed around [UTF-8](libluna/include/luna/Utf8.h).
- Environment-agnostic [utility library](libluna/), which can be used in both kernel and userspace.
- Return-oriented [error propagation](libluna/include/luna/Result.h), inspired by Rust and SerenityOS.
- Build system uses [CMake](CMakeLists.txt).
## Setup
To build and run Luna, you will need to build a [GCC Cross-Compiler](https://wiki.osdev.org/Why_do_I_need_a_Cross_Compiler) and cross-binutils for `x86_64-luna`. (Yes, Luna is advanced enough that it can use its own [OS-Specific Toolchain](https://wiki.osdev.org/OS_Specific_Toolchain), instead of a bare metal target like `x86_64-elf`. It is the first of my OS projects to be able to do so. The patches for Binutils and GCC are [binutils.patch](tools/binutils.patch) and [gcc.patch](tools/gcc.patch)).
To build and run Luna, you will need to build a [cross-compiler](https://wiki.osdev.org/Why_do_I_need_a_Cross_Compiler) and cross-binutils for `x86_64-luna`.
You should start by installing the [required dependencies](https://wiki.osdev.org/GCC_Cross_Compiler#Installing_Dependencies).
For this, you should start by installing the [required dependencies](https://wiki.osdev.org/GCC_Cross_Compiler#Installing_Dependencies).
Then, run `tools/setup.sh` to build the toolchain.
This script will check whether you have the required versions of the toolchain already setup, and will skip building them if so. (This means that it is used by the build scripts to install the toolchain if it is missing before building, so you could skip running it manually.)
Please beware that building GCC and Binutils can take some time, depending on your machine.
## Building
There are a variety of scripts for building Luna.
`tools/build.sh` will build the kernel, libc and binaries.
`tools/rebuild.sh` will do a full rebuild of the kernel, libc and binaries.
`tools/install.sh` will install those to the system root and initial ramdisk.
`tools/sync-libc.sh` will install the libc headers to the system root, build libc and install it.
`tools/build-iso.sh` will build, install, and make an ISO disk image named Luna.iso.
`tools/build-stable-iso.sh` does the same thing as build-iso.sh, but configures the kernel so that the version does not show the commit hash (used for stable versions).
`tools/rebuild-iso.sh` will do a clean rebuild, install, and make an ISO disk image.
In most cases, you should just use `run.sh`, but if you want to build without running, `build-iso.sh`.
## Running
You should have [QEMU](https://www.qemu.org/) installed.
To run Luna in a virtual machine, you should have [QEMU](https://www.qemu.org/) installed.
You can choose between 3 run scripts:
Additionally, the build process needs some extra dependencies to run: `cmake`, `ninja`, `nasm` and `genext2fs`.
`tools/run.sh` is the one you should use in most cases. It will build changed files, install, make an ISO image, and run Luna in QEMU.
`tools/run.sh` is the script you should use in most cases. It will build changed files, install, make an ISO image, and run Luna in QEMU.
`tools/rebuild-and-run.sh` will rebuild, install, make an ISO, and run Luna in QEMU.
`tools/debug.sh` will run Luna in QEMU with a port open for GDB to connect to. (run `tools/build-debug.sh`, `tools/gdb.sh`, and then `tools/debug.sh` in a separate terminal for an optimal debugging experience)
Essentially, since `run.sh` builds the toolchain if it hasn't been built, builds Luna if it hasn't been built, and runs it, you could just checkout this repo, run `run.sh`, and you're done. No need for the other scripts. Those are included for more fine-grained control/building step-by-step.
You can pass any arguments you want to the run scripts, and those will be forwarded to QEMU. Example: `tools/run.sh -m 512M -net none -machine q35`.
If you have no toolchain set up, `run.sh` will build it automatically, which means that you don't necessarily have to run `setup.sh` since `run.sh` does it for you.
## Prebuilt images
Prebuilt ISO images (numbered) for every version can be found at [pub.cloudapio.eu](https://pub.cloudapio.eu/luna/releases).
These images are built manually whenever I decide to make a new version, and thus don't reflect the latest changes on the `main` branch.
These images are built manually whenever a new release is created, and thus don't reflect the latest changes on the `main` branch.
Every hour, my server pulls the latest commits on `main` and builds an hourly ISO image. The ten most recent ones can be found in the [hourly](https://pub.cloudapio.eu/luna/hourly) directory, and [Luna-latest.iso](https://pub.cloudapio.eu/luna/Luna-latest.iso) should always be symlinked to the newest one.
Every hour, this server pulls the latest commits on `main` and builds an hourly ISO image. The ten most recent ones can be found in the [hourly](https://pub.cloudapio.eu/luna/hourly) directory, and [Luna-latest.iso](https://pub.cloudapio.eu/luna/Luna-latest.iso) should always be symlinked to the newest one.
These images do reflect the latest changes on the `main` branch, but are obviously less stable. Additionally, an hourly image will be skipped if building the latest commit of the project fails.
## Is there third-party software I can use on Luna?
Not right now, but hopefully we can start porting some software soon!
There is no infrastructure for porting third-party software nor there are any patches in the repo for now, but some third-party programs can run on Luna, including [my own library](https://git.cloudapio.eu/apio/minitar).
## License
Luna is open-source and free software under the [BSD-2 License](LICENSE).

View File

@ -4,13 +4,18 @@ function(luna_app SOURCE_FILE APP_NAME)
add_dependencies(${APP_NAME} libc)
target_include_directories(${APP_NAME} PRIVATE ${LUNA_BASE}/usr/include)
target_link_libraries(${APP_NAME} PRIVATE os)
install(TARGETS ${APP_NAME} DESTINATION ${LUNA_ROOT}/initrd/bin)
install(TARGETS ${APP_NAME} DESTINATION ${LUNA_BASE}/usr/bin)
endfunction()
add_executable(preinit preinit.cpp)
target_compile_options(preinit PRIVATE -Os ${COMMON_FLAGS} -Wno-write-strings)
add_dependencies(preinit libc)
target_include_directories(preinit PRIVATE ${LUNA_BASE}/usr/include)
install(TARGETS preinit DESTINATION ${LUNA_ROOT}/initrd/bin)
luna_app(init.cpp init)
luna_app(env.cpp env)
luna_app(su.cpp su)
luna_app(sh.cpp sh)
luna_app(cat.cpp cat)
luna_app(date.cpp date)
luna_app(edit.cpp edit)
@ -24,6 +29,7 @@ luna_app(uname.cpp uname)
luna_app(base64.cpp base64)
luna_app(login.cpp login)
luna_app(ipc-test.cpp ipc-test)
luna_app(signal-test.cpp signal-test)
luna_app(mount.cpp mount)
luna_app(umount.cpp umount)
luna_app(ps.cpp ps)
@ -32,3 +38,7 @@ luna_app(ln.cpp ln)
luna_app(mktemp.cpp mktemp)
luna_app(sysfuzz.cpp sysfuzz)
luna_app(pivot_root.cpp pivot_root)
luna_app(cp.cpp cp)
luna_app(kill.cpp kill)
luna_app(gol.cpp gol)
luna_app(buffer-test.cpp buffer-test)

27
apps/buffer-test.cpp Normal file
View File

@ -0,0 +1,27 @@
#include <assert.h>
#include <stdio.h>
#include <unistd.h>
int main()
{
fprintf(stderr, "Writing incomplete line to stdout (_IOLBF=%d)...\n", stdout->_buf.mode);
fputs("hi!", stdout);
sleep(3);
putchar('\n');
fprintf(stderr, "Incomplete line should have been written.\n");
FILE* f = fopen("/dev/console", "w+");
assert(f);
assert(setvbuf(f, NULL, _IOFBF, 0) == 0);
fprintf(stderr, "Writing long text to file (_IOFBF=%d)...\n", f->_buf.mode);
fputs("Hello world!\nHow are you doing!\nThis is a test for many lines of buffering.\n", f);
sleep(3);
fflush(f);
fprintf(stderr, "Long text should have been written.\n");
fclose(f);
}

View File

@ -23,17 +23,13 @@ static Result<void> do_cat(StringView path)
Result<int> luna_main(int argc, char** argv)
{
StringView filename;
Vector<StringView> files;
os::ArgumentParser parser;
parser.add_description("Concatenate files to standard output."_sv);
parser.add_system_program_info("cat"_sv);
parser.add_positional_argument(filename, "file"_sv, "-"_sv);
parser.set_vector_argument(files);
parser.parse(argc, argv);
TRY(do_cat(filename));
parser.set_vector_argument(files, "files"_sv, "-"_sv);
TRY(parser.parse(argc, argv));
for (auto file : files) TRY(do_cat(file));

74
apps/cp.cpp Normal file
View File

@ -0,0 +1,74 @@
#include <luna/PathParser.h>
#include <luna/String.h>
#include <os/ArgumentParser.h>
#include <os/File.h>
#include <os/FileSystem.h>
#include <os/Prompt.h>
using os::File;
Result<void> copy_file(StringView source, StringView destination, bool verbose, bool interactive)
{
SharedPtr<File> source_file = TRY(os::File::open(source, File::ReadOnly));
SharedPtr<File> destination_file;
struct stat st;
TRY(os::FileSystem::stat(source_file->fd(), st, true));
umask(0);
if (os::FileSystem::is_directory(destination, true))
{
auto basename = TRY(PathParser::basename(source));
auto path = TRY(PathParser::join_paths(destination, basename.view()));
if (interactive && os::FileSystem::exists(path.view(), false))
{
auto prompt = TRY(String::format("cp: Overwrite %s with %s?"_sv, path.chars(), source.chars()));
if (!os::conditional_prompt(prompt.view(), os::DefaultNo)) return {};
}
destination_file = TRY(File::open_or_create(path.view(), File::ReadWrite, st.st_mode & ~S_IFMT));
}
else
{
if (interactive && os::FileSystem::exists(destination, false))
{
auto prompt = TRY(String::format("cp: Overwrite %s with %s?"_sv, destination.chars(), source.chars()));
if (!os::conditional_prompt(prompt.view(), os::DefaultNo)) return {};
}
destination_file = TRY(File::open_or_create(destination, File::ReadWrite, st.st_mode & ~S_IFMT));
}
if (verbose) os::eprintln("copying %s to %s", source.chars(), destination.chars());
auto buf = TRY(Buffer::create_sized(4096));
while (1)
{
TRY(source_file->read(buf, 4096));
if (buf.is_empty()) break;
destination_file->write(buf);
}
return {};
}
Result<int> luna_main(int argc, char** argv)
{
Vector<StringView> files;
StringView destination;
bool verbose { false };
bool interactive { false };
os::ArgumentParser parser;
parser.add_description("Copy files or directories."_sv);
parser.add_system_program_info("cp"_sv);
parser.set_vector_argument(files, "files"_sv, true);
parser.add_positional_argument(destination, "destination"_sv, true);
parser.add_switch_argument(verbose, 'v', "verbose"_sv, "show more information"_sv);
parser.add_switch_argument(interactive, 'i', "interactive"_sv, "prompt before overwriting existing files"_sv);
TRY(parser.parse(argc, argv));
for (const auto& file : files) { TRY(copy_file(file, destination, verbose, interactive)); }
return 0;
}

166
apps/gol.cpp Normal file
View File

@ -0,0 +1,166 @@
#include <alloca.h>
#include <assert.h>
#include <fcntl.h>
#include <luna/Heap.h>
#include <os/ArgumentParser.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/ioctl.h>
#include <time.h>
#include <unistd.h>
struct Cell
{
bool state;
bool new_state;
};
static int g_num_rows = 76;
static int g_num_columns = 102;
static int g_fb_width;
static int g_fb_height;
static int g_fd;
static Cell* g_cells;
static Result<void> fill_cells()
{
g_cells = (Cell*)TRY(calloc_impl(g_num_rows, g_num_columns * sizeof(Cell), false));
for (isize i = 0; i < (g_num_rows * g_num_columns); i++)
{
auto value = rand() % 2;
g_cells[i].state = g_cells[i].new_state = value;
}
return {};
}
static Cell& find_cell(int row, int column)
{
assert(row < g_num_rows);
assert(column < g_num_columns);
return g_cells[row * g_num_columns + column];
}
static constexpr int BYTES_PER_PIXEL = 4;
static char* g_buf;
static void draw_cells()
{
lseek(g_fd, 0, SEEK_SET);
const int CELL_WIDTH = g_fb_width / g_num_columns;
const int CELL_HEIGHT = g_fb_height / g_num_rows;
for (int i = 0; i < g_num_rows; i++)
{
memset(g_buf, 0, g_fb_width * BYTES_PER_PIXEL);
for (int j = 0; j < g_num_columns; j++)
{
auto& cell = find_cell(i, j);
if (cell.state) memset(g_buf + (j * CELL_WIDTH * BYTES_PER_PIXEL), 0xff, CELL_WIDTH * BYTES_PER_PIXEL);
}
for (int j = 0; j < CELL_HEIGHT; j++) { write(g_fd, g_buf, g_fb_width * BYTES_PER_PIXEL); }
}
}
static int find_neighbors(int row, int column)
{
int sum = 0;
if (row > 0 && column > 0) sum += find_cell(row - 1, column - 1).state;
if (row > 0) sum += find_cell(row - 1, column).state;
if (row > 0 && (column + 1) < g_num_columns) sum += find_cell(row - 1, column + 1).state;
if (column > 0) sum += find_cell(row, column - 1).state;
if ((column + 1) < g_num_columns) sum += find_cell(row, column + 1).state;
if ((row + 1) < g_num_rows && column > 0) sum += find_cell(row + 1, column - 1).state;
if ((row + 1) < g_num_rows) sum += find_cell(row + 1, column).state;
if ((row + 1) < g_num_rows && (column + 1) < g_num_columns) sum += find_cell(row + 1, column + 1).state;
return sum;
}
static void next_generation()
{
for (int i = 0; i < g_num_rows; i++)
{
for (int j = 0; j < g_num_columns; j++)
{
auto& cell = find_cell(i, j);
int neighbors = find_neighbors(i, j);
if (!cell.state && neighbors == 3) cell.new_state = true;
else if (cell.state && (neighbors < 2 || neighbors > 3))
cell.new_state = false;
}
}
for (isize i = 0; i < (g_num_rows * g_num_columns); i++) g_cells[i].state = g_cells[i].new_state;
}
Result<int> luna_main(int argc, char** argv)
{
u64 delay_between_iterations = 250;
u64 delay_at_end = 3000;
u64 num_iterations = 100;
StringView columns;
StringView rows;
StringView delay;
StringView end_delay;
StringView iterations;
StringView seed;
os::ArgumentParser parser;
parser.add_description("A framebuffer-based implementation for Conway's Game of Life.");
parser.add_system_program_info("gol"_sv);
parser.add_positional_argument(rows, "rows"_sv, "76"_sv);
parser.add_positional_argument(columns, "columns"_sv, "102"_sv);
parser.add_value_argument(delay, 'd', "delay"_sv, "the delay between generations (in ms)");
parser.add_value_argument(end_delay, 'e', "end-delay"_sv,
"after finishing, how much to wait before returning to the shell (in ms)");
parser.add_value_argument(iterations, 'i', "iterations"_sv, "how many generations to show (default: 100)");
parser.add_value_argument(seed, 's', "seed"_sv, "the seed for the random number generator");
parser.parse(argc, argv);
g_num_columns = (int)TRY(columns.to_uint());
g_num_rows = (int)TRY(rows.to_uint());
if (!delay.is_empty()) delay_between_iterations = TRY(delay.to_uint());
if (!end_delay.is_empty()) delay_at_end = TRY(end_delay.to_uint());
if (!iterations.is_empty()) num_iterations = TRY(iterations.to_uint());
if (!seed.is_empty()) srand((unsigned)TRY(seed.to_uint()));
else
srand((unsigned)time(NULL));
g_fd = open("/dev/fb0", O_WRONLY);
if (g_fd < 0)
{
perror("gol: cannot open framebuffer for writing");
return 1;
}
g_fb_height = ioctl(g_fd, FB_GET_HEIGHT);
g_fb_width = ioctl(g_fd, FB_GET_WIDTH);
TRY(fill_cells());
g_buf = (char*)TRY(calloc_impl(g_fb_width, BYTES_PER_PIXEL));
draw_cells();
while (num_iterations--)
{
usleep(delay_between_iterations * 1000);
next_generation();
draw_cells();
}
usleep(delay_at_end * 1000);
return 0;
}

View File

@ -7,6 +7,7 @@
#include <os/Directory.h>
#include <os/File.h>
#include <os/Process.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
@ -16,7 +17,19 @@
#include <sys/sysmacros.h>
#include <unistd.h>
FILE* g_init_log;
FILE* g_init_log = nullptr;
// Request a successful exit from the system (for tests)
void sigterm_handler(int)
{
_exit(0);
}
// Request a failure exit from the system (for tests)
void sigquit_handler(int)
{
_exit(1);
}
struct Service
{
@ -33,6 +46,16 @@ struct Service
Vector<Service> g_services;
static void do_log(const char* format, ...)
{
va_list ap;
va_start(ap, format);
if (g_init_log) vfprintf(g_init_log, format, ap);
va_end(ap);
}
static Result<void> service_child(const Service& service, SharedPtr<os::File> output, SharedPtr<os::File> error,
SharedPtr<os::File> input)
{
@ -82,23 +105,23 @@ static Result<void> try_start_service(Service& service)
auto rc = service_child(service, new_stdout, new_stderr, new_stdin);
if (rc.has_error())
{
fprintf(g_init_log, "[child %d] failed to start service %s due to error: %s\n", getpid(),
service.name.chars(), rc.error_string());
do_log("[child %d] failed to start service %s due to error: %s\n", getpid(), service.name.chars(),
rc.error_string());
}
fclose(g_init_log);
exit(127);
}
fprintf(g_init_log, "[init] created new child process %d for service %s\n", pid, service.name.chars());
do_log("[init] created new child process %d for service %s\n", pid, service.name.chars());
if (service.wait)
{
fprintf(g_init_log, "[init] waiting for child process %d to finish\n", pid);
do_log("[init] waiting for child process %d to finish\n", pid);
int status;
TRY(os::Process::wait(pid, &status));
fprintf(g_init_log, "[init] child process %d exited with code %d\n", pid, WEXITSTATUS(status));
do_log("[init] child process %d exited with code %d\n", pid, WEXITSTATUS(status));
}
else
service.pid = pid;
@ -111,14 +134,13 @@ static void start_service(Service& service)
auto rc = try_start_service(service);
if (rc.has_error())
{
fprintf(g_init_log, "[init] failed to start service %s due to error: %s\n", service.name.chars(),
rc.error_string());
do_log("[init] failed to start service %s due to error: %s\n", service.name.chars(), rc.error_string());
}
}
static Result<void> load_service(const os::Path& path)
{
fprintf(g_init_log, "[init] reading service file: %s\n", path.name().chars());
do_log("[init] reading service file: %s\n", path.name().chars());
auto file = TRY(os::File::open(path, os::File::ReadOnly));
@ -135,7 +157,7 @@ static Result<void> load_service(const os::Path& path)
auto parts = TRY(line.split_once('='));
if (parts.size() < 2 || parts[0].is_empty() || parts[1].is_empty())
{
fprintf(g_init_log, "[init] file contains invalid line, aborting: '%s'\n", line.chars());
do_log("[init] file contains invalid line, aborting: '%s'\n", line.chars());
return {};
}
@ -145,11 +167,17 @@ static Result<void> load_service(const os::Path& path)
continue;
}
if (parts[0].view() == "Description")
{
// We let users specify this in the config file, but init doesn't actually use it.
continue;
}
if (parts[0].view() == "Command")
{
if (!service.command.is_empty())
{
fprintf(g_init_log, "[init] 'Command' cannot be specified after 'Script' has already been set! (%s)\n",
do_log("[init] 'Command' cannot be specified after 'Script' has already been set! (%s)\n",
line.chars());
return {};
}
@ -161,7 +189,7 @@ static Result<void> load_service(const os::Path& path)
{
if (!service.command.is_empty())
{
fprintf(g_init_log, "[init] 'Script' cannot be specified after 'Command' has already been set! (%s)\n",
do_log("[init] 'Script' cannot be specified after 'Command' has already been set! (%s)\n",
line.chars());
return {};
}
@ -215,22 +243,22 @@ static Result<void> load_service(const os::Path& path)
continue;
}
fprintf(g_init_log, "[init] skipping unknown entry name %s\n", parts[0].chars());
do_log("[init] skipping unknown entry name %s\n", parts[0].chars());
}
if (service.name.is_empty())
{
fprintf(g_init_log, "[init] service file is missing 'Name' entry, aborting!\n");
do_log("[init] service file is missing 'Name' entry, aborting!\n");
return {};
}
if (service.command.is_empty())
{
fprintf(g_init_log, "[init] service file is missing 'Command' or 'Script' entry, aborting!\n");
do_log("[init] service file is missing 'Command' or 'Script' entry, aborting!\n");
return {};
}
fprintf(g_init_log, "[init] loaded service %s into memory\n", service.name.chars());
do_log("[init] loaded service %s into memory\n", service.name.chars());
TRY(g_services.try_append(move(service)));
@ -254,7 +282,7 @@ static Result<void> start_services()
TRY(load_services());
for (auto& service : g_services)
{
fprintf(g_init_log, "[init] starting service %s\n", service.name.chars());
do_log("[init] starting service %s\n", service.name.chars());
start_service(service);
}
@ -270,16 +298,16 @@ static Result<void> set_hostname()
if (sethostname(hostname.chars(), hostname.length()) < 0) return {};
fprintf(g_init_log, "[init] successfully set system hostname to '%s'\n", hostname.chars());
do_log("[init] successfully set system hostname to '%s'\n", hostname.chars());
return {};
}
static void mount_devfs()
static void mount_tmpfs()
{
if (mkdir("/dev", 0755) < 0 && errno != EEXIST) exit(255);
if (mount("/tmp", "tmpfs", "tmpfs") < 0) exit(255);
if (mount("/dev", "devfs") < 0) exit(255);
if (chmod("/tmp", 01777) < 0) exit(255);
}
int main()
@ -290,21 +318,24 @@ int main()
return 1;
}
mount_devfs();
// Before this point, we don't even have an stdin, stdout and stderr. Set it up now so that child processes (and us)
// can print stuff.
stdin = fopen("/dev/console", "r");
stdout = fopen("/dev/console", "w");
stderr = fopen("/dev/console", "w");
mount_tmpfs();
umask(022);
g_init_log = fopen("/init.log", "w+");
g_init_log = fopen("/dev/uart0", "w+");
fcntl(fileno(g_init_log), F_SETFD, FD_CLOEXEC);
set_hostname();
if (signal(SIGTERM, sigterm_handler) == SIG_ERR) do_log("[init] failed to register handler for SIGTERM");
if (signal(SIGQUIT, sigquit_handler) == SIG_ERR) do_log("[init] failed to register handler for SIGQUIT");
start_services();
while (1)
@ -319,12 +350,18 @@ int main()
{
if (service.pid.has_value() && service.pid.value() == child)
{
fprintf(g_init_log, "[init] service %s exited with status %d\n", service.name.chars(),
WEXITSTATUS(status));
if (WIFEXITED(status))
{
do_log("[init] service %s exited with status %d\n", service.name.chars(), WEXITSTATUS(status));
}
else
{
do_log("[init] service %s was terminated by signal %d\n", service.name.chars(), WTERMSIG(status));
}
if (service.restart)
{
fprintf(g_init_log, "[init] restarting service %s\n", service.name.chars());
do_log("[init] restarting service %s\n", service.name.chars());
start_service(service);
}

23
apps/kill.cpp Normal file
View File

@ -0,0 +1,23 @@
#include <os/ArgumentParser.h>
#include <os/Process.h>
#include <stdlib.h>
Result<int> luna_main(int argc, char** argv)
{
StringView signo_sv = "15";
StringView process;
os::ArgumentParser parser;
parser.add_description("Send a signal to another process."_sv);
parser.add_system_program_info("kill"_sv);
parser.add_value_argument(signo_sv, 's', "signal", "the signal number to send");
parser.add_positional_argument(process, "pid", true);
parser.parse(argc, argv);
int signo = atoi(signo_sv.chars());
pid_t pid = atoi(process.chars());
TRY(os::Process::kill(pid, signo));
return 0;
}

View File

@ -1,7 +1,9 @@
#include <luna/String.h>
#include <os/ArgumentParser.h>
#include <os/File.h>
#include <signal.h>
#include <stdio.h>
#include <termios.h>
#include <unistd.h>
Result<int> luna_main(int argc, char** argv)
@ -26,6 +28,10 @@ Result<int> luna_main(int argc, char** argv)
if (username.is_empty())
{
signal(SIGTTOU, SIG_IGN);
if (isatty(STDIN_FILENO)) tcsetpgrp(STDIN_FILENO, getpgid(0));
auto input = os::File::standard_input();
os::print("Username: ");

View File

@ -6,15 +6,17 @@ Result<int> luna_main(int argc, char** argv)
{
StringView target;
StringView fstype { "auto" };
StringView source;
os::ArgumentParser parser;
parser.add_description("Mount a file system.");
parser.add_system_program_info("mount"_sv);
parser.add_positional_argument(source, "source"_sv, true);
parser.add_positional_argument(target, "mountpoint"_sv, true);
parser.add_value_argument(fstype, 't', "type"_sv, "the file system type to use");
parser.parse(argc, argv);
if (mount(target.chars(), fstype.chars()) < 0)
if (mount(target.chars(), fstype.chars(), source.chars()) < 0)
{
perror("mount");
return 1;

86
apps/preinit.cpp Normal file
View File

@ -0,0 +1,86 @@
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/mount.h>
#include <sys/stat.h>
#include <sys/syscall.h>
#include <unistd.h>
// FIXME: Make this configurable.
#define LUNA_ROOT_PARTITION "/dev/cd0p2"
static void mount_devfs()
{
if (mkdir("/dev", 0755) < 0 && errno != EEXIST && errno != EROFS) exit(255);
if (mount("/dev", "devfs", "devfs") < 0) exit(255);
}
static void open_std_streams()
{
stdin = fopen("/dev/console", "r");
stdout = fopen("/dev/console", "w");
stderr = fopen("/dev/console", "w");
}
static void fail(const char* message)
{
open_std_streams();
fprintf(stderr, "preinit: fatal error: %s\n", message);
exit(255);
}
static void fail_errno(const char* message)
{
int err = errno;
open_std_streams();
fprintf(stderr, "preinit: fatal error: %s (%s)\n", message, strerror(err));
exit(255);
}
static void mount_rootfs()
{
if (mkdir("/osroot", 0755) < 0 && errno != EEXIST) exit(255);
if (mount("/osroot", "ext2", LUNA_ROOT_PARTITION) < 0) fail_errno("Cannot mount the root partition");
}
int main()
{
if (getpid() != 1)
{
fprintf(stderr, "error: preinit must be run as the init process.\n");
return 1;
}
mount_devfs();
mount_rootfs();
struct stat st;
if (stat("/osroot/mnt", &st) < 0 || !S_ISDIR(st.st_mode)) fail("No suitable temporary mountpoint for pivot_root");
// Now that we have mounted the root file system, remove the /dev mount on the current ramfs root.
umount("/dev");
long rc = syscall(SYS_pivot_root, "/osroot", "/osroot/mnt");
if (rc < 0) exit(255);
chdir("/");
umount("/mnt");
// Now, mount the /dev file system on the new root.
mount_devfs();
/*setenv("PATH", "/bin:/usr/bin", 1);
char* argv[] = { "init", nullptr };
char* envp[] = { nullptr };
execvpe(argv[0], argv, envp);*/
char* argv[] = { "/usr/bin/init", nullptr };
execv(argv[0], argv);
fail_errno("Failed to execute init");
return 255;
}

View File

@ -1,124 +0,0 @@
#include <errno.h>
#include <luna/String.h>
#include <luna/Vector.h>
#include <os/ArgumentParser.h>
#include <os/File.h>
#include <os/FileSystem.h>
#include <os/Process.h>
#include <pwd.h>
#include <stddef.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/utsname.h>
#include <unistd.h>
using os::File;
static Result<Vector<String>> split_command_into_args(StringView cmd)
{
return cmd.split(" \n"_sv);
}
static Result<void> execute_command(StringView command)
{
if (strcspn(command.chars(), " #") == 0) return {};
auto args = TRY(split_command_into_args(command));
if (args.size() < 1) exit(0);
return os::Process::exec(args[0].view(), args.slice());
}
struct utsname g_sysinfo;
const char* hostname = "";
const char* username = "";
char prompt_end = '$';
Result<int> luna_main(int argc, char** argv)
{
StringView path;
StringView command;
bool interactive { false };
SharedPtr<File> input_file;
os::ArgumentParser parser;
parser.add_description("The Luna system's command shell."_sv);
parser.add_system_program_info("sh"_sv);
parser.add_positional_argument(path, "path"_sv, "-"_sv);
parser.add_value_argument(command, 'c', "command"_sv, "execute a single command and then exit"_sv);
parser.parse(argc, argv);
if (!command.is_empty()) TRY(execute_command(command));
if (path == "-")
{
input_file = File::standard_input();
interactive = true;
}
else
{
input_file = TRY(File::open(path, File::ReadOnly));
input_file->set_close_on_exec();
}
if (interactive)
{
// Set up everything to form a prompt.
uname(&g_sysinfo);
hostname = g_sysinfo.nodename;
if (getuid() == 0) prompt_end = '#';
struct passwd* pw = getpwuid(getuid());
if (pw) { username = pw->pw_name; }
else { username = getenv("USER"); }
endpwent();
}
while (1)
{
if (interactive)
{
auto cwd = TRY(os::FileSystem::working_directory());
os::print("%s@%s:%s%c ", username, hostname, cwd.chars(), prompt_end);
}
auto cmd = TRY(input_file->read_line());
if (cmd.is_empty())
{
if (interactive) puts("exit");
break;
}
if (strspn(cmd.chars(), " \n") == cmd.length()) continue;
if (strcspn(cmd.chars(), " #") == 0) continue;
if (!strncmp(cmd.chars(), "cd", 2))
{
auto args = TRY(split_command_into_args(cmd.view()));
check(args[0].view() == "cd");
if (args.size() == 1)
{
auto home = TRY(os::FileSystem::home_directory());
TRY(os::FileSystem::change_directory(home.view()));
continue;
}
TRY(os::FileSystem::change_directory(args[1].view()));
continue;
}
pid_t child = TRY(os::Process::fork());
if (child == 0) { TRY(execute_command(cmd.view())); }
TRY(os::Process::wait(child, nullptr));
}
return 0;
}

23
apps/signal-test.cpp Normal file
View File

@ -0,0 +1,23 @@
#include <signal.h>
#include <stdio.h>
#include <string.h>
void handler(int)
{
puts("I caught a segfault!");
}
int main()
{
struct sigaction sa;
sa.sa_handler = handler;
sigemptyset(&sa.sa_mask);
sa.sa_flags = SA_RESETHAND;
sigaction(SIGSEGV, &sa, NULL);
#pragma GCC diagnostic ignored "-Wnonnull"
char* str = nullptr;
memset(str, 0, 2);
return 0;
}

View File

@ -1,35 +1,59 @@
#include <bits/termios.h>
#include <os/ArgumentParser.h>
#include <pwd.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/ioctl.h>
#include <termios.h>
#include <unistd.h>
static struct termios orig;
void restore_terminal()
{
ioctl(fileno(stdin), TCSETS, &orig);
tcsetattr(STDIN_FILENO, TCSANOW, &orig);
}
void signal_handler(int signo)
{
restore_terminal();
raise(signo);
}
char* getpass()
{
fputs("Password: ", stdout);
if (ioctl(fileno(stdin), TCGETS, &orig) < 0)
if (!isatty(STDIN_FILENO))
{
perror("ioctl(TCGETS)");
// FIXME: Just read from /dev/tty (the controlling terminal). Problem: that doesn't exist yet.
fprintf(stderr, "error: password must be read from a terminal!");
return nullptr;
}
tcsetpgrp(STDIN_FILENO, getpgid(0));
fputs("Password: ", stdout);
fflush(stdout);
if (tcgetattr(STDIN_FILENO, &orig) < 0)
{
perror("tcgetattr");
return nullptr;
}
struct sigaction sa;
sa.sa_handler = signal_handler;
sigemptyset(&sa.sa_mask);
sa.sa_flags = SA_RESETHAND;
sigaction(SIGINT, &sa, NULL);
sigaction(SIGTERM, &sa, NULL);
sigaction(SIGQUIT, &sa, NULL);
atexit(restore_terminal);
struct termios tc = orig;
tc.c_lflag &= ~ECHO;
if (ioctl(fileno(stdin), TCSETS, &tc) < 0)
if (tcsetattr(STDIN_FILENO, TCSANOW, &tc) < 0)
{
perror("ioctl(TCSETS)");
perror("tcsetattr");
return nullptr;
}
@ -82,6 +106,8 @@ Result<int> luna_main(int argc, char** argv)
if ((prompt_password || getuid() != geteuid()) && *entry->pw_passwd)
{
signal(SIGTTOU, SIG_IGN);
char* pass = getpass();
if (!pass) return 1;
@ -102,6 +128,7 @@ Result<int> luna_main(int argc, char** argv)
chdir(entry->pw_dir);
clearenv();
setenv("PATH", "/bin:/sbin", 1);
setpgid(0, 0);
}
if (login || entry->pw_uid != 0) setenv("USER", entry->pw_name, 1);

View File

@ -20,7 +20,7 @@ int random_syscall()
while (true)
{
sys = rand() % Syscalls::__count;
if (sys == SYS_exit || sys == SYS_usleep || sys == SYS_fork) continue;
if (sys == SYS_exit || sys == SYS_usleep || sys == SYS_fork || sys == SYS_sigreturn) continue;
break;
}

View File

@ -11,7 +11,7 @@ Result<int> luna_main(int argc, char** argv)
os::ArgumentParser parser;
parser.add_description("Time a command.");
parser.add_system_program_info("time"_sv);
parser.set_vector_argument(command, true);
parser.set_vector_argument(command, "command"_sv, true, true);
TRY(parser.parse(argc, argv));
auto pid = TRY(os::Process::fork());

1
base/bin Symbolic link
View File

@ -0,0 +1 @@
usr/bin

4
base/etc/init/00-home Normal file
View File

@ -0,0 +1,4 @@
Name=mount-home
Description=Mount the user's home directory on a writable filesystem.
Script=/etc/startup/mount-home.sh
Wait=true

4
base/etc/init/01-motd Normal file
View File

@ -0,0 +1,4 @@
Name=motd
Description=Show the message of the day to the user.
Command=/usr/bin/cat /etc/motd
Wait=true

4
base/etc/init/99-login Normal file
View File

@ -0,0 +1,4 @@
Name=login
Description=Start the command-line login program.
Command=/usr/bin/login
Restart=true

View File

@ -0,0 +1,2 @@
mount -t tmpfs tmpfs /home/selene
chown selene:selene /home/selene

View File

@ -1,3 +0,0 @@
Name=tmpfs
Script=/sbin/mount-tmpfs
Wait=true

View File

@ -1,3 +0,0 @@
Name=motd
Command=/bin/cat /etc/motd
Wait=true

View File

@ -1,3 +0,0 @@
Name=login
Command=/bin/login
Restart=true

2
initrd/sbin/mount-ext2fs Normal file
View File

@ -0,0 +1,2 @@
mkdir /mnt
mount -t ext2 /dev/cd0p2 /mnt

View File

@ -1,5 +0,0 @@
#!/bin/sh
mkdir -p /tmp
mount -t tmpfs /tmp
chmod 1777 /tmp

View File

@ -12,7 +12,7 @@ set(SOURCES
src/memory/MemoryManager.cpp
src/memory/Heap.cpp
src/memory/KernelVM.cpp
src/memory/UserVM.cpp
src/memory/AddressSpace.cpp
src/memory/MemoryMap.cpp
src/boot/Init.cpp
src/arch/Serial.cpp
@ -40,6 +40,7 @@ set(SOURCES
src/sys/uname.cpp
src/sys/mount.cpp
src/sys/resource.cpp
src/sys/signal.cpp
src/fs/VFS.cpp
src/fs/Pipe.cpp
src/fs/Mount.cpp
@ -47,12 +48,15 @@ set(SOURCES
src/fs/GPT.cpp
src/fs/tmpfs/FileSystem.cpp
src/fs/tmpfs/Inode.cpp
src/fs/ext2/FileSystem.cpp
src/fs/ext2/Inode.cpp
src/fs/devices/DeviceRegistry.cpp
src/fs/devices/NullDevice.cpp
src/fs/devices/ZeroDevice.cpp
src/fs/devices/FullDevice.cpp
src/fs/devices/ConsoleDevice.cpp
src/fs/devices/FramebufferDevice.cpp
src/fs/devices/UARTDevice.cpp
src/fs/InitRD.cpp
src/thread/ELF.cpp
)
@ -103,10 +107,15 @@ if(MOON_DEBUG)
include(debug.cmake)
endif()
if(EXISTS config.cmake)
if(EXISTS ${CMAKE_CURRENT_LIST_DIR}/config.cmake)
message(STATUS "Using custom config.cmake file")
include(config.cmake)
endif()
if(BUILD_TESTS)
target_compile_definitions(moon PRIVATE MOON_ENABLE_TESTING_FEATURES)
endif()
target_link_options(moon PRIVATE -lgcc -Wl,--build-id=none -z max-page-size=0x1000 -mcmodel=kernel)
set_target_properties(moon PROPERTIES CXX_STANDARD 20)

View File

@ -10,3 +10,8 @@
# Example: Adding a compiler flag. This will optimize the kernel aggressively (warning: untested, use at your own discretion).
# target_compile_options(moon PRIVATE -O3)
# Uncomment the line below to allow any filename on file/directory creation (by default, the kernel prohibits filenames with
# control characters, leading/trailing spaces, problematic characters and invalid UTF-8). Keep in mind that this restriction
# is only enforced when creating files; existing files with such illegal filenames are parsed correctly and fully usable.
# target_compile_definitions(moon PRIVATE MOON_DISABLE_FILENAME_RESTRICTIONS)

View File

@ -7,5 +7,8 @@ target_compile_definitions(moon PRIVATE EXEC_DEBUG)
target_compile_definitions(moon PRIVATE OPEN_DEBUG)
target_compile_definitions(moon PRIVATE REAP_DEBUG)
target_compile_definitions(moon PRIVATE PCI_DEBUG)
target_compile_definitions(moon PRIVATE EXT2_DEBUG)
target_compile_definitions(moon PRIVATE DEVICE_REGISTRY_DEBUG)
target_compile_definitions(moon PRIVATE FORK_DEBUG)
target_compile_definitions(moon PRIVATE MOUNT_DEBUG)
target_compile_options(moon PRIVATE -fsanitize=undefined)

View File

@ -35,5 +35,9 @@ namespace CPU
bool register_interrupt(u8 interrupt, void (*handler)(Registers*, void*), void* context);
void sync_interrupts();
#ifdef MOON_ENABLE_TESTING_FEATURES
void magic_exit(int status);
#endif
void pause();
}

View File

@ -3,13 +3,14 @@
namespace Keyboard
{
enum Modifiers
struct KeyboardState
{
LeftControl = 1,
LeftAlt = 2,
bool ignore_next { false };
bool left_shift { false };
bool right_shift { false };
bool left_control { false };
bool capslock { false };
};
Option<char> decode_scancode(u8 scancode);
int modifiers();
Option<char> decode_scancode_tty(u8 scancode, KeyboardState& state);
}

View File

@ -9,6 +9,7 @@
#include "sys/Syscall.h"
#include "thread/Scheduler.h"
#include "video/TextConsole.h"
#include <bits/signal.h>
#include <cpuid.h>
#include <luna/CString.h>
#include <luna/CircularQueue.h>
@ -70,64 +71,65 @@ void decode_page_fault_error_code(u64 code)
(code & PF_RESERVED) ? " | Reserved bits set" : "", (code & PF_NX_VIOLATION) ? " | NX violation" : "");
}
[[noreturn]] void handle_page_fault(Registers* regs)
void handle_cpu_exception(int signo, const char* err, Registers* regs)
{
CPU::disable_interrupts();
if (err) kerrorln("Caught CPU exception: %s", err);
kerrorln("RAX: %.16lx RBX: %.16lx RCX: %.16lx RDX: %.16lx", regs->rax, regs->rbx, regs->rcx, regs->rdx);
kerrorln("RBP: %.16lx RSP: %.16lx RDI: %.16lx RSI: %.16lx", regs->rbp, regs->rsp, regs->rdi, regs->rsi);
kerrorln("R8: %.16lx R9: %.16lx R10: %.16lx R11: %.16lx", regs->r8, regs->r9, regs->r10, regs->r11);
kerrorln("R12: %.16lx R13: %.16lx R14: %.16lx R15: %.16lx", regs->r12, regs->r13, regs->r14, regs->r15);
kerrorln("RIP: %.16lx CS: %.16lx SS: %.16lx FLAGS: %.16lx", regs->rip, regs->cs, regs->ss, regs->rflags);
CPU::print_stack_trace_at(regs);
if (!is_in_kernel(regs))
{
Scheduler::current()->send_signal(signo);
Scheduler::current()->process_pending_signals(regs);
return;
}
CPU::efficient_halt();
}
void handle_page_fault(Registers* regs)
{
u64 cr2;
asm volatile("mov %%cr2, %0" : "=r"(cr2));
kerrorln("Page fault at RIP %lx while accessing %lx!", regs->rip, cr2);
kerrorln("Page fault while accessing %lx!", cr2);
decode_page_fault_error_code(regs->error);
CPU::print_stack_trace_at(regs);
if (!is_in_kernel(regs))
{
// FIXME: Kill this process with SIGSEGV once we have signals and all that.
kerrorln("Current task %zu was terminated because of a page fault", Scheduler::current()->id);
Scheduler::current()->exit_and_signal_parent(127);
handle_cpu_exception(SIGSEGV, nullptr, regs);
}
CPU::efficient_halt();
}
[[noreturn]] void handle_general_protection_fault(Registers* regs)
void handle_general_protection_fault(Registers* regs)
{
CPU::disable_interrupts();
kerrorln("General protection fault at RIP %lx, error code %lx!", regs->rip, regs->error);
CPU::print_stack_trace_at(regs);
if (!is_in_kernel(regs))
{
// FIXME: Kill this process with SIGSEGV once we have signals and all that.
kerrorln("Current task %zu was terminated because of a general protection fault", Scheduler::current()->id);
Scheduler::current()->exit_and_signal_parent(127);
}
CPU::efficient_halt();
handle_cpu_exception(SIGSEGV, nullptr, regs);
}
extern "C" void handle_x86_exception(Registers* regs)
{
CPU::disable_interrupts();
switch (regs->isr)
{
case 0: FIXME_UNHANDLED_INTERRUPT("Division by zero");
case 0: handle_cpu_exception(SIGFPE, "Division by zero", regs); return;
case 1: FIXME_UNHANDLED_INTERRUPT("Debug interrupt");
case 2: FIXME_UNHANDLED_INTERRUPT("NMI (Non-maskable interrupt)");
case 3: FIXME_UNHANDLED_INTERRUPT("Breakpoint");
case 4: FIXME_UNHANDLED_INTERRUPT("Overflow");
case 5: FIXME_UNHANDLED_INTERRUPT("Bound range exceeded");
case 6: FIXME_UNHANDLED_INTERRUPT("Invalid opcode");
case 6: handle_cpu_exception(SIGILL, "Invalid opcode", regs); return;
case 7: FIXME_UNHANDLED_INTERRUPT("Device not available");
case 10: FIXME_UNHANDLED_INTERRUPT("Invalid TSS");
case 11: FIXME_UNHANDLED_INTERRUPT("Segment not present");
case 12: FIXME_UNHANDLED_INTERRUPT("Stack-segment fault");
case 13: handle_general_protection_fault(regs);
case 14: handle_page_fault(regs);
case 16: FIXME_UNHANDLED_INTERRUPT("x87 floating-point exception");
case 13: handle_general_protection_fault(regs); return;
case 14: handle_page_fault(regs); return;
case 16: handle_cpu_exception(SIGFPE, "x87 floating-point exception", regs); return;
case 17: FIXME_UNHANDLED_INTERRUPT("Alignment check");
case 19: FIXME_UNHANDLED_INTERRUPT("SIMD floating-point exception");
case 20: FIXME_UNHANDLED_INTERRUPT("Virtualization exception");
@ -145,8 +147,7 @@ void io_thread()
u8 scancode;
while (!scancode_queue.try_pop(scancode)) kernel_wait_for_event();
char key;
if (Keyboard::decode_scancode(scancode).try_set_value(key)) ConsoleDevice::did_press_key(key);
ConsoleDevice::did_press_or_release_key(scancode);
}
}
@ -184,6 +185,7 @@ extern "C" void arch_interrupt_entry(Registers* regs)
{
SyscallArgs args = { regs->rdi, regs->rsi, regs->rdx, regs->r10, regs->r8, regs->r9 };
regs->rax = (u64)invoke_syscall(regs, args, regs->rax);
Scheduler::current()->process_pending_signals(regs);
}
else
{
@ -324,7 +326,7 @@ namespace CPU
static void backtrace_impl(u64 base_pointer, void (*callback)(u64, void*), void* arg)
{
StackFrame* current_frame = (StackFrame*)base_pointer;
while (current_frame &&
while (current_frame && (u64)current_frame >= 0xFFFF'FFFF'8000'0000 &&
MemoryManager::validate_access(current_frame, sizeof(*current_frame), MemoryManager::DEFAULT_ACCESS) &&
current_frame->instruction)
{
@ -415,6 +417,17 @@ namespace CPU
change_pic_masks(pic1_mask, pic2_mask);
CPU::restore_interrupts(val);
}
#ifdef MOON_ENABLE_TESTING_FEATURES
// For tests! Must run QEMU with -device isa-debug-exit,iobase=0xf4,iosize=0x04.
void magic_exit(int status)
{
IO::outl(0xf4,
status ? 0x2 : 0x1); // QEMU exits with (status << 1) | 1. Zero would map to 0b11 (3), non-zero would
// map to 0b101 (5).
__builtin_unreachable();
}
#endif
}
// called by kernel_yield

View File

@ -18,6 +18,16 @@ struct FPData
void save();
void restore();
void* data()
{
return (void*)m_data;
}
usize size() const
{
return 512;
}
private:
char m_data[512] alignas(16);
bool m_already_saved;

View File

@ -1,4 +1,5 @@
#include "arch/Keyboard.h"
#include <luna/CType.h>
// PS/2 keyboard decoding routine.
@ -28,11 +29,6 @@ static bool should_ignore_key(u8 scancode)
return (scancode > 0x3A && scancode < 0x47) || scancode == TAB || scancode == F11 || scancode == F12;
}
static bool g_ignore_next { false };
static bool g_left_shift { false };
static bool g_right_shift { false };
static bool g_capslock { false };
constexpr char key_table[] = {
'\0',
'\1', // escape
@ -117,28 +113,26 @@ constexpr char shifted_key_table[] = {
'.', // keypad .
};
static bool is_shifted()
static bool is_shifted(const Keyboard::KeyboardState& state)
{
if (g_capslock) return !(g_left_shift || g_right_shift);
return g_left_shift || g_right_shift;
if (state.capslock) return !(state.left_shift || state.right_shift);
return state.left_shift || state.right_shift;
}
int g_modifiers = 0;
namespace Keyboard
{
Option<char> decode_scancode(u8 scancode)
Option<char> decode_scancode_tty(u8 scancode, KeyboardState& state)
{
if (g_ignore_next)
if (state.ignore_next)
{
g_ignore_next = false;
state.ignore_next = false;
return {};
}
// FIXME: Support extended scancodes.
if (scancode == EXTENDED_KEY_CODE)
{
g_ignore_next = true;
state.ignore_next = true;
return {};
}
@ -146,35 +140,27 @@ namespace Keyboard
if (scancode == LEFT_SHIFT)
{
g_left_shift = !released;
state.left_shift = !released;
return {};
}
if (scancode == RIGHT_SHIFT)
{
g_right_shift = !released;
state.right_shift = !released;
return {};
}
if (scancode == CAPS_LOCK)
{
if (!released) g_capslock = !g_capslock;
return {};
}
if (scancode == LEFT_ALT)
{
if (released) g_modifiers &= ~LeftAlt;
else
g_modifiers |= LeftAlt;
if (!released) state.capslock = !state.capslock;
return {};
}
if (scancode == LEFT_CONTROL)
{
if (released) g_modifiers &= ~LeftControl;
if (released) state.left_control = false;
else
g_modifiers |= LeftControl;
state.left_control = true;
return {};
}
@ -182,12 +168,20 @@ namespace Keyboard
if (released) return {};
if (is_shifted()) return shifted_key_table[scancode];
return key_table[scancode];
if (state.left_control)
{
char key;
if (is_shifted(state)) key = shifted_key_table[scancode];
else
key = key_table[scancode];
if (_islower(key)) key = (char)_toupper(key);
if (_isupper(key)) return key - 0x40;
if (key == '@') return key - 0x40;
if (key > 'Z' && key < '`') return key - 0x40;
if (key == '?') return 0x7f;
}
int modifiers()
{
return g_modifiers;
if (is_shifted(state)) return shifted_key_table[scancode];
return key_table[scancode];
}
}

View File

@ -1,5 +1,9 @@
#include "thread/Thread.h"
#include "Log.h"
#include "memory/MemoryManager.h"
#include <luna/Alignment.h>
#include <luna/CString.h>
#include <luna/Check.h>
bool is_in_kernel(Registers* regs)
{
@ -31,6 +35,11 @@ void Thread::set_return(u64 ret)
regs.rax = ret;
}
u64 Thread::return_register()
{
return regs.rax;
}
void Thread::init_regs_kernel()
{
memset(&regs, 0, sizeof(Registers));
@ -61,3 +70,102 @@ void switch_context(Thread* old_thread, Thread* new_thread, Registers* regs)
memcpy(regs, &new_thread->regs, sizeof(Registers));
}
// FIXME: Move this function to a common location (also used in ThreadImage)
Result<u64> Thread::push_mem_on_stack(const u8* mem, usize size)
{
if ((regs.rsp - size) < stack.bottom()) return err(E2BIG);
if (!MemoryManager::validate_user_write((void*)(regs.rsp - size), size)) return err(EFAULT);
regs.rsp -= size;
memcpy((void*)regs.rsp, mem, size);
return regs.rsp;
}
Result<u64> Thread::pop_mem_from_stack(u8* mem, usize size)
{
if ((regs.rsp + size) > stack.top()) return err(E2BIG);
if (!MemoryManager::validate_user_read((void*)regs.rsp, size)) return err(EFAULT);
memcpy(mem, (void*)regs.rsp, size);
regs.rsp += size;
return regs.rsp;
}
bool Thread::deliver_signal(int signo, Registers* current_regs)
{
auto handler = signal_handlers[signo - 1];
check(handler.sa_handler != SIG_IGN);
check(handler.sa_handler != SIG_DFL);
memcpy(&regs, current_regs, sizeof(regs));
kinfoln("signal: delivering signal %d for thread %ld, ip=%p, sp=%p, handler=%p, sigreturn=%p", signo, id,
(void*)regs.rip, (void*)regs.rsp, (void*)handler.sa_handler, (void*)handler.__sa_sigreturn);
regs.rsp -= 128; // Skip the red zone
fp_data.save();
if (push_mem_on_stack((u8*)current_regs, sizeof(*current_regs)).has_error()) return false;
if (push_mem_on_stack((u8*)&signal_mask, sizeof(signal_mask)).has_error()) return false;
if (push_mem_on_stack((u8*)fp_data.data(), fp_data.size()).has_error()) return false;
u64 rsp = regs.rsp;
regs.rsp = align_down<16>(regs.rsp);
if (push_mem_on_stack((u8*)&rsp, sizeof(u64)).has_error()) return false;
if (push_mem_on_stack((u8*)&handler.__sa_sigreturn, sizeof(void*)).has_error()) return false;
signal_mask = handler.sa_mask;
if ((handler.sa_flags & SA_NODEFER) == 0) signal_mask |= (1 << (signo - 1));
rsp = regs.rsp;
init_regs_user();
regs.rsp = rsp;
regs.rip = (u64)handler.sa_handler;
regs.rdi = signo;
memcpy(current_regs, &regs, sizeof(regs));
if (handler.sa_flags & SA_RESETHAND)
{
handler.sa_handler = SIG_DFL;
handler.sa_mask = 0;
handler.sa_flags = 0;
signal_handlers[signo - 1] = handler;
}
return true;
}
void Thread::sigreturn(Registers* current_regs)
{
memcpy(&regs, current_regs, sizeof(regs));
u64 rflags = current_regs->rflags;
u64 rsp;
pop_mem_from_stack((u8*)&rsp, sizeof(rsp));
regs.rsp = rsp;
pop_mem_from_stack((u8*)fp_data.data(), fp_data.size());
pop_mem_from_stack((u8*)&signal_mask, sizeof(signal_mask));
pop_mem_from_stack((u8*)current_regs, sizeof(*current_regs));
memcpy(&regs, current_regs, sizeof(regs));
regs.cs = 0x18 | 3;
regs.ss = 0x20 | 3;
regs.rflags = (rflags & ~0xdff) | (regs.rflags & 0xdff);
fp_data.restore();
kinfoln("sigreturn: restored program state, sp=%p, ip=%p", (void*)regs.rsp, (void*)regs.rip);
memcpy(current_regs, &regs, sizeof(regs));
}

View File

@ -65,9 +65,12 @@ namespace ATA
if (command_new != command_old) PCI::write16(m_device.address, PCI::Command, command_new);
if (!m_primary_channel.initialize()) return false;
bool success = false;
return m_secondary_channel.initialize();
if (m_primary_channel.initialize()) success = true;
if (m_secondary_channel.initialize()) success = true;
return success;
}
void Controller::irq_handler(Registers* regs)
@ -146,7 +149,7 @@ namespace ATA
{
if (drive == m_current_drive) return;
u8 value = (drive << 4) | 0xa0;
u8 value = (u8)(drive << 4) | 0xa0;
write_register(Register::DriveSelect, value);
delay_400ns();

View File

@ -1,6 +1,8 @@
#include "arch/MMU.h"
#include "arch/x86_64/CPU.h"
#include <luna/CString.h>
#include <luna/Check.h>
#include <luna/Stack.h>
#include <luna/Types.h>
struct [[gnu::packed]] GDTR
@ -75,10 +77,13 @@ static void set_tss_base(GDTEntry* tss1, HighGDTEntry* tss2, u64 addr)
tss2->base_high = (u32)(addr >> 32);
}
static u8 alternate_stack[ARCH_PAGE_SIZE * 4];
static void setup_tss()
{
memset(&task_state_segment, 0, sizeof(TSS));
task_state_segment.iomap_base = sizeof(TSS);
task_state_segment.ist[0] = Stack { (u64)alternate_stack, ARCH_PAGE_SIZE * 4 }.top();
set_tss_base(&gdt.tss, &gdt.tss2, (u64)&task_state_segment);
set_limit(&gdt.tss, sizeof(TSS) - 1);
}

View File

@ -47,20 +47,21 @@ struct [[gnu::packed]] IDTR
static_assert(sizeof(IDTR) == 10UL);
static void idt_add_handler(short num, void* handler, u8 type_attr)
static void idt_add_handler(short num, void* handler, u8 type_attr, u8 ist)
{
check(handler != nullptr);
expect(num < 256, "IDT can only hold up to 256 entries");
IDTEntry* const entry_for_handler = &idt[num];
entry_for_handler->selector = 0x08;
entry_for_handler->type_attr = type_attr;
entry_for_handler->ist = ist;
entry_for_handler->set_offset((u64)handler);
}
#define INT(x) extern "C" void _isr##x()
#define TRAP(x) idt_add_handler(x, (void*)_isr##x, IDT_TA_TrapGate)
#define IRQ(x) idt_add_handler(x, (void*)_isr##x, IDT_TA_InterruptGate)
#define SYS(x) idt_add_handler(x, (void*)_isr##x, IDT_TA_UserCallableInterruptGate)
#define TRAP(x) idt_add_handler(x, (void*)_isr##x, IDT_TA_TrapGate, 1)
#define IRQ(x) idt_add_handler(x, (void*)_isr##x, IDT_TA_InterruptGate, 0)
#define SYS(x) idt_add_handler(x, (void*)_isr##x, IDT_TA_UserCallableInterruptGate, 0)
INT(0);
INT(1);

View File

@ -4,3 +4,11 @@
#define MOON_VERSION_MAJOR "@CMAKE_PROJECT_VERSION_MAJOR@"
#define MOON_VERSION_MINOR "@CMAKE_PROJECT_VERSION_MINOR@"
#define MOON_VERSION_PATCH "@CMAKE_PROJECT_VERSION_PATCH@"
#ifndef STRINGIZE_VALUE_OF
#define STRINGIZE(x) #x
#define STRINGIZE_VALUE_OF(x) STRINGIZE(x)
#endif
#define MOON_RELEASE_NAME "@LUNA_RELEASE_NAME@"
#define MOON_RELEASE STRINGIZE_VALUE_OF(MOON_RELEASE_NAME) " " __DATE__ " " __TIME__

View File

@ -29,7 +29,6 @@ namespace GPT
check(header.reserved == 0);
check(header.this_lba == 1);
#if 0
const u32 chksum = checksum_gpt(header);
if (chksum != header.checksum)
@ -37,7 +36,6 @@ namespace GPT
kwarnln("gpt: Header checksum does not match, %#.8x != %#.8x!", chksum, header.checksum);
return false;
}
#endif
u64 partition_table_start = header.partition_table_lba * GPT_SECTOR_SIZE;

View File

@ -101,6 +101,11 @@ namespace VFS
Result<void> validate_filename(StringView name)
{
#ifdef MOON_DISABLE_FILENAME_RESTRICTIONS
(void)name;
return {};
#endif
// Forbid problematic characters that could cause trouble in shell scripts and the like.
if (strpbrk(name.chars(), "*?:[]\"<>\\")) return err(EINVAL);
@ -217,6 +222,7 @@ namespace VFS
g_root_inode = new_root_inode;
TRY(new_root_parent_inode->replace_entry(((MountInode*)g_root_inode.ptr())->source(), new_root_path.chars()));
((MountInode*)g_root_inode.ptr())->set_source({});
g_root_inode->fs()->reset_mount_dir();
return {};
}
@ -227,7 +233,9 @@ namespace VFS
auto parent_path = TRY(PathParser::dirname(path));
auto child = TRY(PathParser::basename(path));
kinfoln("vfs: Mounting filesystem on target %s", path);
#ifdef MOUNT_DEBUG
kdbgln("vfs: Mounting filesystem on target %s", path);
#endif
auto parent_inode = TRY(resolve_path(parent_path.chars(), auth, working_directory));
@ -239,6 +247,8 @@ namespace VFS
TRY(parent_inode->replace_entry(mount, child.chars()));
kinfoln("vfs: Successfully mounted filesystem on target %s", path);
return {};
}

View File

@ -36,6 +36,13 @@ namespace VFS
virtual Result<void> set_mount_dir(SharedPtr<Inode> parent) = 0;
virtual Result<void> reset_mount_dir() = 0;
virtual bool is_readonly() const
{
return false;
}
virtual u64 handles() const
{
return m_handles;
@ -74,6 +81,11 @@ namespace VFS
return err(ENOTTY);
}
virtual Result<u64> isatty() const
{
return err(ENOTTY);
}
// Directory-specific methods
virtual Result<SharedPtr<Inode>> find(const char* name) const = 0;

View File

@ -1,103 +1,194 @@
#include "fs/devices/ConsoleDevice.h"
#include "Log.h"
#include "arch/Keyboard.h"
#include "fs/devices/DeviceRegistry.h"
#include "memory/MemoryManager.h"
#include "thread/Scheduler.h"
#include "video/TextConsole.h"
#include <bits/termios.h>
#include <luna/Buffer.h>
#include <bits/ioctl-defs.h>
#include <luna/CString.h>
#include <luna/CType.h>
#include <luna/Units.h>
#include <luna/Vector.h>
static Buffer g_console_input;
static bool g_eof { false };
static bool g_echo { true };
Vector<SharedPtr<ConsoleDevice>> ConsoleDevice::m_console_devices;
Result<void> ConsoleDevice::create()
{
auto device = (SharedPtr<Device>)TRY(make_shared<ConsoleDevice>());
auto device = TRY(make_shared<ConsoleDevice>());
device->m_settings.c_lflag = ECHO | ECHOE | ECHOCTL | ISIG | ICANON;
device->m_settings.c_cc[VEOF] = '\4';
device->m_settings.c_cc[VERASE] = '\b';
device->m_settings.c_cc[VINTR] = '\3';
device->m_settings.c_cc[VQUIT] = '\x1c';
TRY(m_console_devices.try_append(device));
return DeviceRegistry::register_special_device(DeviceRegistry::Console, 0, device);
}
Result<void> ConsoleDevice::handle_background_process_group(bool can_succeed, int signo) const
{
if (!m_foreground_process_group.has_value()) return {};
auto foreground_pgrp = m_foreground_process_group.value();
auto* current = Scheduler::current();
if ((pid_t)current->pgid == foreground_pgrp) return {};
if ((current->signal_mask & (1 << (signo - 1))) || (current->signal_handlers[signo - 1].sa_handler == SIG_IGN))
{
if (can_succeed) return {};
return err(EIO);
}
current->send_signal(signo);
if (can_succeed) return err(EINTR);
return err(EIO);
}
Result<usize> ConsoleDevice::read(u8* buf, usize, usize length) const
{
if (length > g_console_input.size()) length = g_console_input.size();
TRY(handle_background_process_group(false, SIGTTIN));
memcpy(buf, g_console_input.data(), length);
if (length > m_input_buffer.size()) length = m_input_buffer.size();
memmove(g_console_input.data(), g_console_input.data() + length, g_console_input.size() - length);
memcpy(buf, m_input_buffer.data(), length);
g_console_input.try_resize(g_console_input.size() - length).release_value();
memmove(m_input_buffer.data(), m_input_buffer.data() + length, m_input_buffer.size() - length);
if (!length && g_eof) g_eof = false;
m_input_buffer.try_resize(m_input_buffer.size() - length).release_value();
if (!length && m_may_read_without_blocking) m_may_read_without_blocking = false;
return length;
}
Result<usize> ConsoleDevice::write(const u8* buf, usize, usize length)
{
if (m_settings.c_lflag & TOSTOP) TRY(handle_background_process_group(true, SIGTTOU));
TextConsole::write((const char*)buf, length);
return length;
}
bool ConsoleDevice::blocking() const
{
return g_eof ? false : g_console_input.size() == 0;
return m_may_read_without_blocking ? false : m_input_buffer.size() == 0;
}
static Vector<u8> g_temp_input;
void ConsoleDevice::did_press_key(char key)
void ConsoleDevice::did_press_or_release_key(u8 scancode)
{
if (key == '\b')
{
if (g_temp_input.try_pop().has_value())
{
if (g_echo) TextConsole::putwchar(L'\b');
for (const auto& device : m_console_devices) { device->process_key_event(scancode); }
}
void ConsoleDevice::process_key_event(u8 scancode)
{
auto rc = Keyboard::decode_scancode_tty(scancode, m_kb_state);
if (!rc.has_value()) return;
char key = rc.value();
check(key >= 0);
bool is_special_character { false };
if (is_canonical())
{
if (key == m_settings.c_cc[VERASE])
{
auto maybe_char = m_line_buffer.try_pop();
if (maybe_char.has_value() && maybe_char.value())
{
if ((m_settings.c_lflag & ECHO) && (m_settings.c_lflag & ECHOE))
{
TextConsole::putwchar(L'\b');
if (_iscntrl(maybe_char.value())) TextConsole::putwchar(L'\b');
}
return;
}
// Ctrl+D
if (key == 'd' && (Keyboard::modifiers() & Keyboard::LeftControl))
{
if (g_temp_input.size() == 0) g_eof = true;
return;
if ((m_settings.c_lflag & ECHOE)) return;
else
is_special_character = true;
}
if (key == 'e' && (Keyboard::modifiers() & (Keyboard::LeftAlt | Keyboard::LeftControl)))
if (key == m_settings.c_cc[VEOF])
{
Scheduler::dump_state();
return;
m_input_buffer.append_data(m_line_buffer.data(), m_line_buffer.size());
m_line_buffer.clear();
m_may_read_without_blocking = true;
is_special_character = true;
}
if (key == 'm' && (Keyboard::modifiers() & (Keyboard::LeftAlt | Keyboard::LeftControl)))
if (m_settings.c_lflag & ISIG)
{
kinfoln("Total memory: %s", to_dynamic_unit(MemoryManager::total()).release_value().chars());
kinfoln("Free memory: %s", to_dynamic_unit(MemoryManager::free()).release_value().chars());
kinfoln("Used memory: %s", to_dynamic_unit(MemoryManager::used()).release_value().chars());
kinfoln("Reserved memory: %s", to_dynamic_unit(MemoryManager::reserved()).release_value().chars());
return;
if (key == m_settings.c_cc[VINTR])
{
if (!(m_settings.c_lflag & NOFLSH)) m_line_buffer.clear();
if (m_foreground_process_group.has_value())
{
Scheduler::for_each_in_process_group(m_foreground_process_group.value(), [](Thread* thread) {
thread->send_signal(SIGINT);
return true;
});
}
is_special_character = true;
}
if (key == 'h' && (Keyboard::modifiers() & (Keyboard::LeftAlt | Keyboard::LeftControl)))
if (key == m_settings.c_cc[VQUIT])
{
dump_heap_usage();
return;
if (!(m_settings.c_lflag & NOFLSH)) m_line_buffer.clear();
if (m_foreground_process_group.has_value())
{
Scheduler::for_each_in_process_group(m_foreground_process_group.value(), [](Thread* thread) {
thread->send_signal(SIGQUIT);
return true;
});
}
is_special_character = true;
}
}
}
g_temp_input.try_append((u8)key);
if (!is_special_character)
{
if (is_canonical())
{
m_line_buffer.try_append((u8)key);
if (key == '\n')
{
g_console_input.append_data(g_temp_input.data(), g_temp_input.size());
g_temp_input.clear();
m_input_buffer.append_data(m_line_buffer.data(), m_line_buffer.size());
m_line_buffer.clear();
}
}
else
m_input_buffer.append_data((u8*)&key, 1);
}
if (!g_echo) return;
if (!(m_settings.c_lflag & ECHO)) return;
if (_iscntrl(key))
{
if (m_settings.c_lflag & ECHOCTL)
{
bool should_echo = true;
if (key == '\n' || key == '\0' || key == m_settings.c_cc[VEOF]) should_echo = false;
if (should_echo)
{
char caret_notation[3] = { '^', '\0', '\0' };
if (key == 0x7f) caret_notation[1] = '?';
else
caret_notation[1] = key + 0x40;
TextConsole::print(caret_notation);
}
else
TextConsole::putchar(key);
}
}
else
TextConsole::putchar(key);
}
@ -106,21 +197,42 @@ Result<u64> ConsoleDevice::ioctl(int request, void* arg)
switch (request)
{
case TCGETS: {
struct termios tc
{
.c_lflag = 0
};
if (g_echo) tc.c_lflag |= ECHO;
return MemoryManager::copy_to_user_typed((struct termios*)arg, &tc) ? 0 : err(EFAULT);
return MemoryManager::copy_to_user_typed((struct termios*)arg, &m_settings) ? 0 : err(EFAULT);
}
case TCSETS: {
struct termios tc;
if (!MemoryManager::copy_from_user_typed((const struct termios*)arg, &tc)) return err(EFAULT);
if (tc.c_lflag & ECHO) g_echo = true;
else
g_echo = false;
TRY(handle_background_process_group(true, SIGTTOU));
if (!MemoryManager::copy_from_user_typed((const struct termios*)arg, &m_settings)) return err(EFAULT);
return 0;
}
case TIOCSPGRP: {
TRY(handle_background_process_group(true, SIGTTOU));
pid_t pgid;
if (!MemoryManager::copy_from_user_typed((const pid_t*)arg, &pgid)) return err(EFAULT);
bool pgid_exists = false;
Scheduler::for_each_in_process_group(pgid, [&pgid_exists](Thread*) {
pgid_exists = true;
return false;
});
if (!pgid_exists) return err(EPERM);
m_foreground_process_group = pgid;
return 0;
}
case TIOCGPGRP: {
pid_t pgid = m_foreground_process_group.value_or((pid_t)next_thread_id());
if (!MemoryManager::copy_to_user_typed((pid_t*)arg, &pgid)) return err(EFAULT);
return 0;
}
case TIOCGWINSZ: {
struct winsize window;
window.ws_col = TextConsole::cols();
window.ws_row = TextConsole::rows();
if (!MemoryManager::copy_to_user_typed((struct winsize*)arg, &window)) return err(EFAULT);
return 0;
}
default: return err(EINVAL);

View File

@ -1,5 +1,8 @@
#pragma once
#include "arch/Keyboard.h"
#include "fs/devices/DeviceRegistry.h"
#include <bits/termios.h>
#include <luna/Buffer.h>
class ConsoleDevice : public Device
{
@ -11,7 +14,7 @@ class ConsoleDevice : public Device
Result<usize> write(const u8*, usize, usize) override;
static void did_press_key(char key);
static void did_press_or_release_key(u8 scancode);
bool blocking() const override;
@ -22,5 +25,30 @@ class ConsoleDevice : public Device
return "console";
}
Result<u64> isatty() const override
{
return 1;
}
virtual ~ConsoleDevice() = default;
private:
struct termios m_settings;
mutable Buffer m_input_buffer;
Option<pid_t> m_foreground_process_group {};
Vector<u8> m_line_buffer;
mutable Keyboard::KeyboardState m_kb_state;
static Vector<SharedPtr<ConsoleDevice>> m_console_devices;
void process_key_event(u8 scancode);
mutable bool m_may_read_without_blocking { false };
inline bool is_canonical() const
{
return m_settings.c_lflag & ICANON;
}
Result<void> handle_background_process_group(bool can_succeed, int signo) const;
};

View File

@ -14,6 +14,11 @@ class Device
return err(ENOTTY);
}
virtual Result<u64> isatty() const
{
return err(ENOTTY);
}
virtual usize size() const
{
return 0;

View File

@ -5,6 +5,7 @@
#include "fs/devices/FramebufferDevice.h"
#include "fs/devices/FullDevice.h"
#include "fs/devices/NullDevice.h"
#include "fs/devices/UARTDevice.h"
#include "fs/devices/ZeroDevice.h"
#include "fs/tmpfs/FileSystem.h"
#include "thread/Thread.h"
@ -69,6 +70,7 @@ namespace DeviceRegistry
FullDevice::create();
ConsoleDevice::create();
FramebufferDevice::create();
UARTDevice::create();
return {};
}

View File

@ -15,6 +15,7 @@ namespace DeviceRegistry
Framebuffer = 3,
Disk = 4,
DiskPartition = 5,
Serial = 6,
};
Result<SharedPtr<Device>> fetch_special_device(u32 major, u32 minor);

View File

@ -0,0 +1,14 @@
#include "fs/devices/UARTDevice.h"
#include "arch/Serial.h"
Result<void> UARTDevice::create()
{
auto device = (SharedPtr<Device>)TRY(make_shared<UARTDevice>());
return DeviceRegistry::register_special_device(DeviceRegistry::Serial, 0, device, 0222);
}
Result<usize> UARTDevice::write(const u8* buf, usize, usize length)
{
Serial::write((const char*)buf, length);
return length;
}

View File

@ -0,0 +1,28 @@
#pragma once
#include "fs/devices/DeviceRegistry.h"
class UARTDevice : public Device
{
public:
// Initializer for DeviceRegistry.
static Result<void> create();
Result<usize> read(u8*, usize, usize) const override
{
return 0;
}
Result<usize> write(const u8*, usize, usize) override;
bool blocking() const override
{
return false;
}
StringView device_path() const override
{
return "uart0";
}
virtual ~UARTDevice() = default;
};

View File

@ -0,0 +1,134 @@
#include "fs/ext2/FileSystem.h"
#include "fs/ext2/Inode.h"
#include <luna/Alignment.h>
static VFS::InodeType vfs_type_from_ext2_type(mode_t mode)
{
auto type = mode & 0xf000;
switch (type)
{
case EXT2_FIFO: return VFS::InodeType::FIFO;
case EXT2_CHR: return VFS::InodeType::CharacterDevice;
case EXT2_DIR: return VFS::InodeType::Directory;
case EXT2_BLK: return VFS::InodeType::BlockDevice;
case EXT2_REG: return VFS::InodeType::RegularFile;
case EXT2_LNK: return VFS::InodeType::Symlink;
case EXT2_SOCK: [[fallthrough]]; // TODO: Sockets not supported on Luna at the moment.
default: fail("ext2: Unknown or unsupported inode type");
}
}
namespace Ext2
{
FileSystem::FileSystem()
{
}
Result<SharedPtr<VFS::Inode>> FileSystem::find_inode_by_number(ino_t inum, bool initialize_dir_now)
{
// Inode numbers start at 1.
check(inum <= m_superblock.nr_inodes);
auto maybe_inode = m_inode_cache.try_get(inum);
if (maybe_inode.has_value()) return maybe_inode.value();
const u32 block_group = (u32)((inum - 1) / m_superblock.inodes_per_block_group);
const auto* block_group_descriptor = TRY(find_block_group_descriptor(block_group));
check(block_group_descriptor);
// FIXME: Even if the inode size is bigger (Ext2::FileSystem::m_inode_size), we only read this amount. Enlarge
// the Inode structure to fit this case.
static constexpr usize INODE_SIZE = 128;
const u64 index = (inum - 1) % m_superblock.inodes_per_block_group;
const u64 inode_address = (block_group_descriptor->inode_table_start * m_block_size) + (index * m_inode_size);
auto inode = TRY(adopt_shared_if_nonnull(new (std::nothrow) Ext2::Inode({}, this)));
TRY(m_host_device->read((u8*)&inode->m_raw_inode, inode_address, INODE_SIZE));
inode->m_type = vfs_type_from_ext2_type(inode->m_raw_inode.mode);
inode->m_inum = inum;
#ifdef EXT2_DEBUG
kdbgln("ext2: Read inode %lu with mode %#x (%#x + %#o), size %lu", inum, inode->m_raw_inode.mode,
inode->m_raw_inode.mode & 0xf000, inode->mode(), inode->size());
#endif
m_inode_cache.try_set(inum, inode);
if (initialize_dir_now && inode->m_type == VFS::InodeType::Directory) TRY(inode->lazy_initialize_dir());
return (SharedPtr<VFS::Inode>)inode;
}
Result<const BlockGroupDescriptor*> FileSystem::find_block_group_descriptor(u32 index)
{
check(index < m_block_groups);
auto maybe_desc = m_block_group_descriptor_cache.try_get_ref(index);
if (maybe_desc) return maybe_desc;
const u64 address = (m_superblock.first_data_block + 1) * m_block_size + (index * sizeof(BlockGroupDescriptor));
BlockGroupDescriptor descriptor;
TRY(m_host_device->read((u8*)&descriptor, address, sizeof(descriptor)));
check(TRY(m_block_group_descriptor_cache.try_set(index, descriptor)));
return m_block_group_descriptor_cache.try_get_ref(index);
}
Result<SharedPtr<VFS::FileSystem>> FileSystem::create(SharedPtr<Device> host_device)
{
SharedPtr<FileSystem> fs = TRY(adopt_shared_if_nonnull(new (std::nothrow) FileSystem()));
const usize nread = TRY(host_device->read((u8*)&fs->m_superblock, 1024, 1024));
if (nread != 1024) return err(EINVAL); // Source had an invalid superblock.
if (fs->m_superblock.signature != EXT2_MAGIC) return err(EINVAL); // Source had an invalid superblock.
if (fs->m_superblock.major_version >= 1)
{
auto required = fs->m_superblock.ext_superblock.required_features;
if (required & EXT2_REQUIRED_COMPAT_D_TYPE) fs->m_dirs_have_type_field = true;
required &= ~EXT2_REQUIRED_COMPAT_D_TYPE;
if (required > 0)
{
kwarnln("ext2: File system has required features not supported by the implementation, cannot mount");
return err(EINVAL);
}
fs->m_uses_extended_size = true;
fs->m_inode_size = fs->m_superblock.ext_superblock.inode_size;
}
fs->m_host_device = host_device;
fs->m_block_size = 1024 << fs->m_superblock.log_block_size;
fs->m_block_groups = get_blocks_from_size(fs->m_superblock.nr_blocks, fs->m_superblock.blocks_per_block_group);
#ifdef EXT2_DEBUG
kdbgln("ext2: Mounting new Ext2 file system, block size=%lu, blocks=%u, inodes=%u, block group=(%u blocks, %u "
"inodes), %lu block groups",
fs->m_block_size, fs->m_superblock.nr_blocks, fs->m_superblock.nr_inodes,
fs->m_superblock.blocks_per_block_group, fs->m_superblock.inodes_per_block_group, fs->m_block_groups);
#endif
// Lookup the root inode.
fs->m_root_inode = TRY(fs->find_inode_by_number(2, true));
return (SharedPtr<VFS::FileSystem>)fs;
}
Result<void> FileSystem::set_mount_dir(SharedPtr<VFS::Inode> inode)
{
return m_root_inode->replace_entry(inode, "..");
}
Result<void> FileSystem::reset_mount_dir()
{
return m_root_inode->replace_entry(m_root_inode, "..");
}
}

View File

@ -0,0 +1,197 @@
#pragma once
#include "fs/VFS.h"
#include "fs/devices/DeviceRegistry.h"
#include <luna/HashMap.h>
#define EXT2_MAGIC 0xef53
#define EXT2_REQUIRED_COMPAT_D_TYPE 0x0002
namespace Ext2
{
struct [[gnu::packed]] Superblock
{
u32 nr_inodes;
u32 nr_blocks;
u32 nr_reserved_blocks;
u32 nr_free_blocks;
u32 nr_free_inodes;
u32 first_data_block;
u32 log_block_size;
u32 log_fragment_size;
u32 blocks_per_block_group;
u32 fragments_per_block_group;
u32 inodes_per_block_group;
u32 last_mount_time;
u32 last_write_time;
u16 mounts_since_last_fsck;
u16 mounts_allowed_before_fsck;
u16 signature;
u16 fs_state;
u16 error_action;
u16 minor_version;
u32 last_fsck_time;
u32 fsck_time_interval;
u32 os_id;
u32 major_version;
u16 reserved_block_uid;
u16 reserved_block_gid;
struct [[gnu::packed]]
{
u32 first_non_reserved_inode;
u16 inode_size;
u16 this_block_group;
u32 optional_features;
u32 required_features;
u32 ro_features;
u8 fsid[16];
u8 name[16];
u8 last_mountpoint[64];
u32 compression_algs;
u8 nr_preallocated_blocks_for_files;
u8 nr_preallocated_blocks_for_dirs;
u16 unused;
u8 journal_id[16];
u32 journal_inode;
u32 journal_device;
u32 orphan_inode_head;
} ext_superblock;
u8 padding[1024 - 236];
};
struct [[gnu::packed]] BlockGroupDescriptor
{
u32 block_usage_addr;
u32 inode_usage_addr;
u32 inode_table_start;
u16 nr_free_blocks;
u16 nr_free_inodes;
u16 nr_directories;
u8 padding[32 - 18];
};
struct [[gnu::packed]] RawInode
{
u16 mode;
u16 uid;
u32 size_low;
u32 atime;
u32 create_time;
u32 mtime;
u32 delete_time;
u16 gid;
u16 nlinks;
u32 sectors_used;
u32 flags;
u32 os_specific_1;
u32 direct_pointers[12];
u32 singly_indirect_ptr;
u32 doubly_indirect_ptr;
u32 triply_indirect_ptr;
u32 gen_number;
u32 extended_attrs;
u32 size_high;
u32 frag_addr;
u32 os_specific_2[3];
};
struct [[gnu::packed]] RawDirectoryEntry
{
u32 inum;
u16 size;
union {
u16 name_length;
struct
{
u8 name_length_low;
u8 type;
};
};
char name[4]; // Names should be padded to a multiple of 4 bytes.
};
static_assert(sizeof(Superblock) == 1024);
static_assert(sizeof(BlockGroupDescriptor) == 32);
static_assert(sizeof(RawInode) == 128);
class Inode;
class FileSystem : public VFS::FileSystem
{
public:
SharedPtr<VFS::Inode> root_inode() const override
{
return m_root_inode;
}
Result<SharedPtr<VFS::Inode>> create_file_inode() override
{
return err(EROFS);
}
Result<SharedPtr<VFS::Inode>> create_dir_inode(SharedPtr<VFS::Inode>) override
{
return err(EROFS);
}
Result<SharedPtr<VFS::Inode>> create_device_inode(u32, u32) override
{
return err(EROFS);
}
Result<SharedPtr<VFS::Inode>> create_symlink_inode(StringView) override
{
return err(EROFS);
}
Result<void> set_mount_dir(SharedPtr<VFS::Inode>) override;
Result<void> reset_mount_dir() override;
bool is_readonly() const override
{
return true;
}
static Result<SharedPtr<VFS::FileSystem>> create(SharedPtr<Device> host_device);
dev_t host_device_id() const override
{
return m_host_device_id;
}
Result<SharedPtr<VFS::Inode>> find_inode_by_number(ino_t inode, bool initialize_dir_now = false);
Result<const BlockGroupDescriptor*> find_block_group_descriptor(u32 index);
virtual ~FileSystem() = default;
private:
FileSystem();
SharedPtr<VFS::Inode> m_root_inode;
SharedPtr<Device> m_host_device;
dev_t m_host_device_id;
Superblock m_superblock;
u64 m_block_size;
u64 m_block_groups;
u32 m_inode_size { 128 };
bool m_dirs_have_type_field { false };
bool m_uses_extended_size { false };
// FIXME: This inode cache will keep all inodes in it alive despite having no other references to it, but we're
// not worrying about that as for now the filesystem implementation is read-only.
HashMap<ino_t, SharedPtr<VFS::Inode>> m_inode_cache;
HashMap<u32, BlockGroupDescriptor> m_block_group_descriptor_cache;
friend class Inode;
};
}

View File

@ -0,0 +1,205 @@
#include "fs/ext2/Inode.h"
#include <luna/Buffer.h>
namespace Ext2
{
Inode::Inode(Badge<FileSystem>, FileSystem* fs) : m_fs(fs)
{
}
usize Inode::size() const
{
return (m_fs->m_uses_extended_size && (m_type == VFS::InodeType::RegularFile))
? ((u64)m_raw_inode.size_high << 32) | (u64)m_raw_inode.size_low
: m_raw_inode.size_low;
}
Result<usize> Inode::read(u8* buf, usize offset, usize length) const
{
if (length == 0) return 0;
if (offset > size()) return 0;
if (offset + length > size()) length = size() - offset;
const usize block_size = m_fs->m_block_size;
usize to_read = length;
if (offset % block_size)
{
usize block_offset = (offset % block_size);
usize block = TRY(find_block(offset / block_size));
usize size_to_read = block_size - block_offset;
if (size_to_read > to_read) size_to_read = to_read;
usize host_offset = (block * block_size) + block_offset;
// FIXME: Cache this data.
TRY(m_fs->m_host_device->read(buf, host_offset, size_to_read));
to_read -= size_to_read;
buf += size_to_read;
offset += size_to_read;
}
while (to_read >= block_size)
{
usize block = TRY(find_block(offset / block_size));
usize host_offset = block * block_size;
// FIXME: Cache this data.
TRY(m_fs->m_host_device->read(buf, host_offset, block_size));
to_read -= block_size;
buf += block_size;
offset += block_size;
}
if (to_read > 0)
{
usize block = TRY(find_block(offset / block_size));
usize host_offset = block * block_size;
// FIXME: Cache this data.
TRY(m_fs->m_host_device->read(buf, host_offset, to_read));
}
return length;
}
Result<usize> Inode::find_block(usize index) const
{
if (index < 12) return m_raw_inode.direct_pointers[index];
usize block_index = (index - 12) * sizeof(u32);
if (block_index >= m_fs->m_block_size)
{
fail("ext2: Finding blocks beyond the singly indirect pointer block is not yet supported");
}
usize block_size = m_fs->m_block_size;
if (m_singly_indirect_block.is_empty())
{
TRY(m_singly_indirect_block.try_resize(block_size));
TRY(m_fs->m_host_device->read(m_singly_indirect_block.data(), m_raw_inode.singly_indirect_ptr * block_size,
block_size));
}
return *reinterpret_cast<u32*>(&m_singly_indirect_block.data()[block_index]);
}
Result<void> Inode::lazy_initialize_dir() const
{
check(m_type == VFS::InodeType::Directory);
const usize inode_size = size();
const usize block_size = m_fs->m_block_size;
u8* const buf = TRY(make_array<u8>(block_size));
auto guard = make_scope_guard([buf] { delete[] buf; });
m_entries.clear();
for (usize offset = 0; offset < inode_size; offset += block_size)
{
TRY(read(buf, offset, block_size));
usize dir_offset = 0;
while (dir_offset < block_size)
{
auto& entry = *(Ext2::RawDirectoryEntry*)&buf[dir_offset];
if (entry.inum != 0)
{
auto inode = TRY(m_fs->find_inode_by_number(entry.inum));
VFS::DirectoryEntry vfs_entry { inode, "" };
vfs_entry.name.adopt(entry.name,
m_fs->m_dirs_have_type_field ? entry.name_length_low : entry.name_length);
#ifdef EXT2_DEBUG
kdbgln("ext2: Read new directory entry: inum=%u, name=%s, namelen=%lu", entry.inum,
vfs_entry.name.chars(), vfs_entry.name.length());
#endif
TRY(m_entries.try_append(move(vfs_entry)));
}
dir_offset += entry.size;
}
}
m_dir_already_lazily_initialized = true;
return {};
}
Result<void> Inode::replace_entry(SharedPtr<VFS::Inode> inode, const char* name)
{
if (m_type != VFS::InodeType::Directory) return err(ENOTDIR);
if (!m_dir_already_lazily_initialized) TRY(lazy_initialize_dir());
for (auto& entry : m_entries)
{
if (!strcmp(name, entry.name.chars()))
{
entry.inode = inode;
return {};
}
}
return err(ENOENT);
}
Result<SharedPtr<VFS::Inode>> Inode::find(const char* name) const
{
if (m_type != VFS::InodeType::Directory) return err(ENOTDIR);
if (!m_dir_already_lazily_initialized) TRY(lazy_initialize_dir());
for (const auto& entry : m_entries)
{
if (!strcmp(name, entry.name.chars())) return entry.inode;
}
return err(ENOENT);
}
Option<VFS::DirectoryEntry> Inode::get(usize index) const
{
if (m_type != VFS::InodeType::Directory) return {};
if (!m_dir_already_lazily_initialized)
if (lazy_initialize_dir().has_error()) return {};
if (index >= m_entries.size()) return {};
return m_entries[index];
}
Result<StringView> Inode::readlink()
{
check(m_type == VFS::InodeType::Symlink);
if (!m_link.is_empty()) return m_link.view();
const usize length = size();
if (length < 60)
{
// The symlink location is stored inline within the inode's data blocks.
m_link = TRY(String::from_string_view(
StringView::from_fixed_size_cstring((char*)&m_raw_inode.direct_pointers[0], length)));
return m_link.view();
}
Buffer buf = TRY(Buffer::create_sized(length));
TRY(read(buf.data(), 0, length));
m_link = TRY(String::from_string_view(StringView::from_fixed_size_cstring((char*)buf.data(), length)));
return m_link.view();
}
}

156
kernel/src/fs/ext2/Inode.h Normal file
View File

@ -0,0 +1,156 @@
#pragma once
#include "fs/ext2/FileSystem.h"
#include <luna/Buffer.h>
#include <luna/String.h>
#define EXT2_FIFO 0x1000
#define EXT2_CHR 0x2000
#define EXT2_DIR 0x4000
#define EXT2_BLK 0x6000
#define EXT2_REG 0x8000
#define EXT2_LNK 0xA000
#define EXT2_SOCK 0xC000
namespace Ext2
{
class Inode : public VFS::Inode
{
public:
VFS::InodeType type() const override
{
return m_type;
}
usize size() const override;
mode_t mode() const override
{
return m_raw_inode.mode & 07777;
}
nlink_t nlinks() const override
{
return m_raw_inode.nlinks;
}
u32 uid() const override
{
return m_raw_inode.uid;
}
u32 gid() const override
{
return m_raw_inode.gid;
}
usize inode_number() const override
{
return m_inum;
}
VFS::FileSystem* fs() const override
{
return m_fs;
}
void did_link() override
{
}
void did_unlink() override
{
}
Result<void> chmod(mode_t) override
{
return err(EROFS);
}
Result<void> chown(u32, u32) override
{
return err(EROFS);
}
Result<usize> read(u8* buf, usize offset, usize length) const override;
Result<usize> write(const u8*, usize, usize) override
{
return err(EROFS);
}
Result<void> truncate(usize) override
{
return err(EROFS);
}
Result<SharedPtr<VFS::Inode>> find(const char*) const override;
Option<VFS::DirectoryEntry> get(usize) const override;
Result<SharedPtr<VFS::Inode>> create_file(const char*) override
{
if (m_type != VFS::InodeType::Directory) return err(ENOTDIR);
return err(EROFS);
}
Result<SharedPtr<VFS::Inode>> create_subdirectory(const char*) override
{
if (m_type != VFS::InodeType::Directory) return err(ENOTDIR);
return err(EROFS);
}
Result<void> add_entry(SharedPtr<VFS::Inode>, const char*) override
{
if (m_type != VFS::InodeType::Directory) return err(ENOTDIR);
return err(EROFS);
}
Result<void> replace_entry(SharedPtr<VFS::Inode>, const char*) override;
Result<void> remove_entry(const char*) override
{
if (m_type != VFS::InodeType::Directory) return err(ENOTDIR);
return err(EROFS);
}
usize entries() const override
{
return m_entries.size();
}
bool blocking() const override
{
return false;
}
Result<StringView> readlink() override;
Result<void> lazy_initialize_dir() const;
// FIXME: Implement device numbers.
Inode(Badge<FileSystem>, FileSystem* fs);
virtual ~Inode() = default;
private:
VFS::InodeType m_type;
RawInode m_raw_inode;
FileSystem* m_fs;
ino_t m_inum;
mutable Buffer m_singly_indirect_block;
String m_link;
mutable Vector<VFS::DirectoryEntry> m_entries;
mutable bool m_dir_already_lazily_initialized { false };
Result<usize> find_block(usize index) const;
friend class FileSystem;
};
}

View File

@ -72,6 +72,11 @@ namespace TmpFS
return m_root_inode->replace_entry(parent, "..");
}
Result<void> FileSystem::reset_mount_dir()
{
return m_root_inode->replace_entry(m_root_inode, "..");
}
void FileSystem::set_root(SharedPtr<VFS::Inode> root)
{
m_root_inode = root;

View File

@ -20,6 +20,8 @@ namespace TmpFS
Result<void> set_mount_dir(SharedPtr<VFS::Inode> parent) override;
Result<void> reset_mount_dir() override;
static Result<SharedPtr<VFS::FileSystem>> create();
dev_t host_device_id() const override

View File

@ -276,6 +276,11 @@ namespace TmpFS
return m_device->ioctl(request, arg);
}
Result<u64> isatty() const override
{
return m_device->isatty();
}
bool blocking() const override
{
return m_device->blocking();

View File

@ -40,19 +40,15 @@ void reap_thread()
kinfoln("Current platform: %s", CPU::platform_string().chars());
kinfoln("Current processor: %s", CPU::identify().value_or("(unknown)"_sv).chars());
kinfoln("Total memory: %s", to_dynamic_unit(MemoryManager::total()).release_value().chars());
kinfoln("Free memory: %s", to_dynamic_unit(MemoryManager::free()).release_value().chars());
kinfoln("Used memory: %s", to_dynamic_unit(MemoryManager::used()).release_value().chars());
kinfoln("Reserved memory: %s", to_dynamic_unit(MemoryManager::reserved()).release_value().chars());
auto root = mark_critical(TmpFS::FileSystem::create(), "Failed to create initial ramfs");
mark_critical(VFS::mount_root(root), "Failed to mount the initial ramfs as the root filesystem");
mark_critical(InitRD::populate_vfs(), "Failed to load files from the initial ramdisk");
mark_critical(DeviceRegistry::init(), "Failed to register initial devices");
auto init = mark_critical(VFS::resolve_path("/bin/init", Credentials {}), "Can't find init in the initial ramfs!");
auto init =
mark_critical(VFS::resolve_path("/bin/preinit", Credentials {}), "Can't find init in the initial ramfs!");
auto init_thread =
mark_critical(Scheduler::new_userspace_thread(init, "/bin/init"), "Failed to create PID 1 process for init");
mark_critical(Scheduler::new_userspace_thread(init, "/bin/preinit"), "Failed to create PID 1 process for init");
auto reap = mark_critical(Scheduler::new_kernel_thread(reap_thread, "[reap]"),
"Failed to create the process reaper kernel thread");

View File

@ -1,4 +1,4 @@
#include "memory/UserVM.h"
#include "memory/AddressSpace.h"
#include "Log.h"
#include "arch/MMU.h"
#include "memory/Heap.h"
@ -8,17 +8,19 @@
static constexpr u64 VM_START = ARCH_PAGE_SIZE;
static constexpr u64 VM_END = 0x0000800000000000;
Result<OwnedPtr<UserVM>> UserVM::try_create()
Result<OwnedPtr<AddressSpace>> AddressSpace::try_create()
{
OwnedPtr<UserVM> ptr = TRY(make_owned<UserVM>());
OwnedPtr<AddressSpace> ptr = TRY(make_owned<AddressSpace>());
TRY(ptr->create_null_region());
TRY(ptr->create_default_region());
ptr->m_directory = TRY(MMU::create_page_directory_for_userspace());
return move(ptr);
}
Result<void> UserVM::create_null_region()
Result<void> AddressSpace::create_null_region()
{
// Create a small region at the start of the address space to prevent anyone from mapping page 0.
auto* region = TRY(make<VMRegion>());
@ -31,7 +33,7 @@ Result<void> UserVM::create_null_region()
return {};
}
Result<void> UserVM::create_default_region()
Result<void> AddressSpace::create_default_region()
{
// Create a free region covering the rest of the address space.
auto* region = TRY(make<VMRegion>());
@ -43,9 +45,9 @@ Result<void> UserVM::create_default_region()
return {};
}
Result<OwnedPtr<UserVM>> UserVM::clone()
Result<OwnedPtr<AddressSpace>> AddressSpace::clone()
{
OwnedPtr<UserVM> ptr = TRY(make_owned<UserVM>());
OwnedPtr<AddressSpace> ptr = TRY(make_owned<AddressSpace>());
for (const auto* region : m_regions)
{
@ -54,14 +56,32 @@ Result<OwnedPtr<UserVM>> UserVM::clone()
ptr->m_regions.append(new_region);
}
ptr->m_directory = TRY(MMU::clone_userspace_page_directory(m_directory));
return move(ptr);
}
UserVM::UserVM()
AddressSpace::AddressSpace()
{
}
Result<u64> UserVM::alloc_region(usize count, bool persistent)
AddressSpace& AddressSpace::operator=(AddressSpace&& other)
{
if (&other == this) return *this;
m_regions.consume([](VMRegion* region) { delete region; });
if (m_directory) MMU::delete_userspace_page_directory(m_directory);
m_regions = other.m_regions;
m_directory = other.m_directory;
other.m_regions.reset();
other.m_directory = nullptr;
return *this;
}
Result<u64> AddressSpace::alloc_region(usize count, bool persistent)
{
for (auto* region = m_regions.expect_last(); region; region = m_regions.previous(region).value_or(nullptr))
{
@ -91,7 +111,7 @@ Result<u64> UserVM::alloc_region(usize count, bool persistent)
return err(ENOMEM);
}
Result<bool> UserVM::set_region(u64 address, usize count, bool used, bool persistent)
Result<bool> AddressSpace::set_region(u64 address, usize count, bool used, bool persistent)
{
if (address >= VM_END) return err(EINVAL);
@ -154,7 +174,7 @@ Result<bool> UserVM::set_region(u64 address, usize count, bool used, bool persis
return true;
}
void UserVM::merge_contiguous_regions(VMRegion* a, VMRegion* b)
void AddressSpace::merge_contiguous_regions(VMRegion* a, VMRegion* b)
{
a->end = b->end;
a->count += b->count;
@ -162,7 +182,7 @@ void UserVM::merge_contiguous_regions(VMRegion* a, VMRegion* b)
delete b;
}
void UserVM::try_merge_region_with_neighbors(VMRegion* region)
void AddressSpace::try_merge_region_with_neighbors(VMRegion* region)
{
auto prev = m_regions.previous(region);
if (prev.has_value() && (*prev)->used == region->used && (*prev)->persistent == region->persistent)
@ -178,7 +198,7 @@ void UserVM::try_merge_region_with_neighbors(VMRegion* region)
}
}
Result<VMRegion*> UserVM::split_region(VMRegion* parent, u64 boundary)
Result<VMRegion*> AddressSpace::split_region(VMRegion* parent, u64 boundary)
{
auto* region = TRY(make<VMRegion>());
@ -195,7 +215,8 @@ Result<VMRegion*> UserVM::split_region(VMRegion* parent, u64 boundary)
return region;
}
UserVM::~UserVM()
AddressSpace::~AddressSpace()
{
m_regions.consume([](VMRegion* region) { delete region; });
if (m_directory) MMU::delete_userspace_page_directory(m_directory);
}

View File

@ -1,4 +1,5 @@
#pragma once
#include "arch/MMU.h"
#include <luna/LinkedList.h>
#include <luna/OwnedPtr.h>
#include <luna/Result.h>
@ -13,11 +14,13 @@ class VMRegion : LinkedListNode<VMRegion>
bool persistent { false };
};
class UserVM
class AddressSpace
{
public:
UserVM();
~UserVM();
AddressSpace();
~AddressSpace();
AddressSpace& operator=(AddressSpace&& other);
Result<u64> alloc_region(usize count, bool persistent = false);
@ -31,9 +34,14 @@ class UserVM
return set_region(address, count, false, false);
}
static Result<OwnedPtr<UserVM>> try_create();
static Result<OwnedPtr<AddressSpace>> try_create();
Result<OwnedPtr<UserVM>> clone();
Result<OwnedPtr<AddressSpace>> clone();
PageDirectory* page_directory() const
{
return m_directory;
}
private:
Result<bool> set_region(u64 address, usize count, bool used, bool persistent);
@ -42,5 +50,7 @@ class UserVM
void try_merge_region_with_neighbors(VMRegion* region);
void merge_contiguous_regions(VMRegion* a, VMRegion* b);
Result<VMRegion*> split_region(VMRegion* parent, u64 boundary);
LinkedList<VMRegion> m_regions;
PageDirectory* m_directory;
};

View File

@ -151,6 +151,16 @@ namespace MemoryManager
return index * ARCH_PAGE_SIZE;
}
Result<u64> alloc_zeroed_frame()
{
const u64 frame = TRY(alloc_frame());
const u64 address = MMU::translate_physical_address(frame);
memset((void*)address, 0, ARCH_PAGE_SIZE);
return frame;
}
Result<void> free_frame(u64 frame)
{
const u64 index = frame / ARCH_PAGE_SIZE;
@ -263,15 +273,24 @@ namespace MemoryManager
Result<u64> alloc_at_zeroed(u64 virt, usize count, int flags)
{
u64 address = TRY(alloc_at(virt, count, MMU::ReadWrite));
CHECK_PAGE_ALIGNED(virt);
memset((void*)address, 0, count * ARCH_PAGE_SIZE);
u64 start = virt;
usize pages_mapped = 0;
// This should never fail (we just mapped memory at that address) but we don't want to crash the kernel if it
// does.
TRY(remap(address, count, flags));
auto guard = make_scope_guard([=, &pages_mapped] { unmap_owned(start, pages_mapped); });
return address;
while (pages_mapped < count)
{
const u64 frame = TRY(alloc_zeroed_frame());
TRY(MMU::map(virt, frame, flags, MMU::UseHugePages::No));
virt += ARCH_PAGE_SIZE;
pages_mapped++;
}
guard.deactivate();
return start;
}
Result<u64> alloc_for_kernel(usize count, int flags)
@ -431,7 +450,7 @@ namespace MemoryManager
TRY(result.try_append(0)); // null terminator
return String::from_cstring(result.data());
return String { result.release_data() };
}
bool validate_access(const void* mem, usize size, int flags)

View File

@ -13,6 +13,7 @@ namespace MemoryManager
Result<void> protect_kernel_sections();
Result<u64> alloc_frame();
Result<u64> alloc_zeroed_frame();
Result<void> free_frame(u64 frame);
Result<void> free_frames(u64 address, usize count);

View File

@ -1,4 +1,5 @@
#include "sys/Syscall.h"
#include "thread/Scheduler.h"
#include <luna/SystemError.h>
syscall_func_t syscalls[] = {
@ -13,6 +14,7 @@ i64 invoke_syscall(Registers* regs, SyscallArgs args, u64 syscall)
if (syscall >= Syscalls::__count) { return -ENOSYS; }
auto rc = syscalls[syscall](regs, args);
if (rc.has_error()) return -rc.error();
return (i64)rc.value();
}

View File

@ -70,7 +70,7 @@ Result<u64> sys_execve(Registers* regs, SyscallArgs args)
kdbgln("exec: attempting to replace current image with %s", path.chars());
#endif
auto guard = make_scope_guard([current] { MMU::switch_page_directory(current->directory); });
auto guard = make_scope_guard([current] { MMU::switch_page_directory(current->self_directory()); });
auto image = TRY(ThreadImage::try_load_from_elf(inode));
@ -99,8 +99,6 @@ Result<u64> sys_execve(Registers* regs, SyscallArgs args)
}
}
MMU::delete_userspace_page_directory(current->directory);
if (VFS::is_setuid(inode)) current->auth.euid = current->auth.suid = inode->uid();
if (VFS::is_setgid(inode)) current->auth.egid = current->auth.sgid = inode->gid();
@ -108,12 +106,19 @@ Result<u64> sys_execve(Registers* regs, SyscallArgs args)
image->apply(current);
MMU::switch_page_directory(current->directory);
MMU::switch_page_directory(current->self_directory());
current->set_arguments(user_argc, user_argv, user_envc, user_envp);
memcpy(regs, &current->regs, sizeof(*regs));
for (int i = 0; i < NSIG; i++)
{
current->signal_handlers[i] = { .sa_handler = SIG_DFL, .sa_mask = 0, .sa_flags = 0 };
}
current->has_called_exec = true;
kinfoln("exec: thread %lu was replaced with %s", current->id, path.chars());
return 0;
@ -123,7 +128,7 @@ Result<u64> sys_fork(Registers* regs, SyscallArgs)
{
auto current = Scheduler::current();
auto guard = make_scope_guard([current] { MMU::switch_page_directory(current->directory); });
auto guard = make_scope_guard([current] { MMU::switch_page_directory(current->self_directory()); });
memcpy(&current->regs, regs, sizeof(*regs));
@ -153,6 +158,13 @@ Result<u64> sys_fork(Registers* regs, SyscallArgs)
memcpy(&thread->regs, regs, sizeof(*regs));
for (int i = 0; i < NSIG; i++)
{
auto sighandler = current->signal_handlers[i].sa_handler;
thread->signal_handlers[i] = { .sa_handler = sighandler, .sa_mask = 0, .sa_flags = 0 };
}
thread->signal_mask = current->signal_mask;
thread->set_return(0);
Scheduler::add_thread(thread);

View File

@ -10,7 +10,7 @@
#include <luna/SafeArithmetic.h>
#include <sys/types.h>
Result<u64> sys_read(Registers*, SyscallArgs args)
Result<u64> sys_read(Registers* regs, SyscallArgs args)
{
int fd = (int)args[0];
u8* buf = (u8*)args[1];
@ -31,6 +31,13 @@ Result<u64> sys_read(Registers*, SyscallArgs args)
if (descriptor.should_block()) kernel_sleep(10);
else
return err(EAGAIN);
if (current->interrupted)
{
kdbgln("signal: read interrupted by signal");
if (current->will_invoke_signal_handler()) return err(EINTR);
current->process_pending_signals(regs);
}
}
usize nread = TRY(descriptor.inode->read(buf, descriptor.offset, size));
@ -157,6 +164,16 @@ Result<u64> sys_ioctl(Registers*, SyscallArgs args)
return descriptor.inode->ioctl(request, arg);
}
Result<u64> sys_isatty(Registers*, SyscallArgs args)
{
int fd = (int)args[0];
Thread* current = Scheduler::current();
auto& descriptor = *TRY(current->resolve_fd(fd));
return descriptor.inode->isatty();
}
Result<u64> sys_dup2(Registers*, SyscallArgs args)
{
int oldfd = (int)args[0];

View File

@ -95,6 +95,53 @@ Result<u64> sys_setegid(Registers*, SyscallArgs args)
return 0;
}
Result<u64> sys_setpgid(Registers*, SyscallArgs args)
{
pid_t pid = (pid_t)args[0];
pid_t pgid = (pid_t)args[1];
auto* current = Scheduler::current();
if (pid == 0) pid = (pid_t)current->id;
if (pgid == 0) pgid = (pid_t)current->id;
if (pgid < 0) return err(EINVAL);
auto* thread = TRY(Result<Thread*>::from_option(Scheduler::find_by_pid(pid), ESRCH));
if (thread != current && thread->parent != current) return err(ESRCH);
// FIXME: Weird session stuff, we don't have that currently.
if (thread->has_called_exec) return err(EPERM);
if (pgid != (pid_t)current->id)
{
bool pgid_exists = false;
Scheduler::for_each_in_process_group(pgid, [&pgid_exists](Thread*) {
pgid_exists = true;
return false;
});
if (!pgid_exists) return err(EPERM);
}
thread->pgid = (u64)pgid;
return 0;
}
Result<u64> sys_getpgid(Registers*, SyscallArgs args)
{
pid_t pid = (pid_t)args[0];
auto* current = Scheduler::current();
if (pid == 0) pid = (pid_t)current->id;
if (pid < 0) return err(EINVAL);
auto* thread = TRY(Result<Thread*>::from_option(Scheduler::find_by_pid(pid), ESRCH));
return (u64)thread->pgid;
}
Result<u64> sys_fchmodat(Registers*, SyscallArgs args)
{
int dirfd = (int)args[0];

View File

@ -37,12 +37,12 @@ Result<u64> sys_mmap(Registers*, SyscallArgs args)
Thread* current = Scheduler::current();
u64 address;
if (!addr) address = TRY(current->vm_allocator->alloc_region(get_blocks_from_size(len, ARCH_PAGE_SIZE)));
if (!addr) address = TRY(current->address_space->alloc_region(get_blocks_from_size(len, ARCH_PAGE_SIZE)));
else
{
// FIXME: We should be more flexible if MAP_FIXED was not specified.
address = align_down<ARCH_PAGE_SIZE>((u64)addr);
if (!TRY(current->vm_allocator->test_and_alloc_region(address, get_blocks_from_size(len, ARCH_PAGE_SIZE))))
if (!TRY(current->address_space->test_and_alloc_region(address, get_blocks_from_size(len, ARCH_PAGE_SIZE))))
return err(ENOMEM);
}
@ -69,7 +69,7 @@ Result<u64> sys_munmap(Registers*, SyscallArgs args)
Thread* current = Scheduler::current();
bool ok = TRY(current->vm_allocator->free_region(address, get_blocks_from_size(size, ARCH_PAGE_SIZE)));
bool ok = TRY(current->address_space->free_region(address, get_blocks_from_size(size, ARCH_PAGE_SIZE)));
// POSIX says munmap should silently do nothing if the memory was not already mapped.
if (!ok) return 0;

View File

@ -1,4 +1,5 @@
#include "fs/VFS.h"
#include "fs/ext2/FileSystem.h"
#include "fs/tmpfs/FileSystem.h"
#include "memory/MemoryManager.h"
#include "sys/Syscall.h"
@ -8,15 +9,28 @@ Result<u64> sys_mount(Registers*, SyscallArgs args)
{
auto target = TRY(MemoryManager::strdup_from_user(args[0]));
auto fstype = TRY(MemoryManager::strdup_from_user(args[1]));
auto source = TRY(MemoryManager::strdup_from_user(args[2]));
auto* current = Scheduler::current();
if (current->auth.euid != 0) return err(EPERM);
auto get_source = [current, &source]() -> Result<SharedPtr<Device>> {
auto inode = TRY(VFS::resolve_path(source.chars(), current->auth, current->current_directory));
if (inode->type() != VFS::InodeType::BlockDevice) return err(ENOTBLK);
dev_t device_id = inode->device_id();
return TRY(DeviceRegistry::fetch_special_device(luna_dev_major(device_id), luna_dev_minor(device_id)));
};
SharedPtr<VFS::FileSystem> fs;
if (fstype.view() == "tmpfs") fs = TRY(TmpFS::FileSystem::create());
else if (fstype.view() == "devfs")
fs = TRY(DeviceRegistry::create_devfs_instance());
else if (fstype.view() == "ext2")
{
auto source_device = TRY(get_source());
fs = TRY(Ext2::FileSystem::create(source_device));
}
else
return err(ENODEV);

View File

@ -63,6 +63,8 @@ Result<u64> sys_openat(Registers*, SyscallArgs args)
inode->chown(current->auth.euid, current->auth.egid);
}
if ((flags & O_WRONLY) && inode->fs() && inode->fs()->is_readonly()) return err(EROFS);
if (inode->type() != VFS::InodeType::Directory && (flags & O_DIRECTORY)) return err(ENOTDIR);
if (inode->type() == VFS::InodeType::Directory)

96
kernel/src/sys/signal.cpp Normal file
View File

@ -0,0 +1,96 @@
#include "Log.h"
#include "memory/MemoryManager.h"
#include "sys/Syscall.h"
#include "thread/Scheduler.h"
#include <bits/signal.h>
Result<u64> sys_sigreturn(Registers* regs, SyscallArgs)
{
auto* current = Scheduler::current();
current->sigreturn(regs);
// Since we could be returning to anywhere in the program, we don't want to be changing the return value register
// (RAX on x86_64). But our syscall framework doesn't allow that, so we just set it to its current value.
return current->return_register();
}
Result<u64> sys_sigaction(Registers*, SyscallArgs args)
{
auto* current = Scheduler::current();
int signo = (int)args[0];
const struct sigaction* act = (const struct sigaction*)args[1];
struct sigaction* oldact = (struct sigaction*)args[2];
void* sigreturn = (void*)args[3];
if (signo > NSIG) return err(EINVAL);
if (signo <= 0) return err(EINVAL);
if (oldact)
{
if (!MemoryManager::copy_to_user_typed(oldact, &current->signal_handlers[signo - 1])) return err(EFAULT);
}
if (act)
{
struct sigaction kact;
if (!MemoryManager::copy_from_user_typed(act, &kact)) return err(EFAULT);
kact.__sa_sigreturn = sigreturn;
current->signal_handlers[signo - 1] = kact;
}
return 0;
}
Result<u64> sys_kill(Registers*, SyscallArgs args)
{
auto* current = Scheduler::current();
pid_t pid = (pid_t)args[0];
int signo = (int)args[1];
// FIXME: Support this case.
if (pid <= 0) return err(ENOTSUP);
auto* target = TRY(Result<Thread*>::from_option(Scheduler::find_by_pid(pid), ESRCH));
if (current->auth.euid != 0 && current->auth.euid != target->auth.euid && current->auth.egid != target->auth.egid)
return err(EPERM);
if (target->is_kernel) return 0;
if (signo == 0) return 0;
target->send_signal(signo);
return 0;
}
Result<u64> sys_sigprocmask(Registers*, SyscallArgs args)
{
auto* current = Scheduler::current();
int how = (int)args[0];
const sigset_t* set = (const sigset_t*)args[1];
sigset_t* oldset = (sigset_t*)args[2];
if (oldset)
{
if (!MemoryManager::copy_to_user_typed(oldset, &current->signal_mask)) return err(EFAULT);
}
if (set)
{
sigset_t kset;
if (!MemoryManager::copy_from_user_typed(set, &kset)) return err(EFAULT);
switch (how)
{
case SIG_BLOCK: current->signal_mask |= kset; break;
case SIG_UNBLOCK: current->signal_mask &= ~kset; break;
case SIG_SETMASK: current->signal_mask = kset; break;
default: return err(EINVAL);
}
}
return 0;
}

View File

@ -1,3 +1,4 @@
#include "Log.h"
#include "arch/CPU.h"
#include "config.h"
#include "memory/MemoryManager.h"
@ -25,7 +26,7 @@ Result<u64> sys_uname(Registers*, SyscallArgs args)
strncpy(result.release, MOON_VERSION, _UTSNAME_LENGTH);
// FIXME: Hardcode this at build time instead of in code (should be the short commit hash).
strncpy(result.version, "alpha", _UTSNAME_LENGTH);
strncpy(result.version, MOON_RELEASE, _UTSNAME_LENGTH);
strncpy(result.machine, CPU::platform_string().chars(), _UTSNAME_LENGTH);
@ -51,5 +52,7 @@ Result<u64> sys_sethostname(Registers*, SyscallArgs args)
s_hostname.adopt(new_hostname);
kinfoln("System hostname updated to '%s'", s_hostname.chars());
return 0;
}

View File

@ -11,5 +11,7 @@ Result<u64> sys_usleep(Registers*, SyscallArgs args)
kernel_sleep(us / 1000);
return 0;
auto* current = Scheduler::current();
return current->sleep_ticks_left;
}

View File

@ -1,9 +1,10 @@
#include "Log.h"
#include "memory/MemoryManager.h"
#include "sys/Syscall.h"
#include "thread/Scheduler.h"
#include <bits/waitpid.h>
Result<u64> sys_waitpid(Registers*, SyscallArgs args)
Result<u64> sys_waitpid(Registers* regs, SyscallArgs args)
{
pid_t pid = (pid_t)args[0];
int* status_ptr = (int*)args[1];
@ -21,7 +22,16 @@ Result<u64> sys_waitpid(Registers*, SyscallArgs args)
if (options & WNOHANG) return err(EAGAIN);
wait_for_child:
if (thread->state != ThreadState::Exited) kernel_wait(pid);
if (current->interrupted)
{
kdbgln("signal: waitpid interrupted by signal");
if (current->will_invoke_signal_handler()) return err(EINTR);
current->process_pending_signals(regs);
goto wait_for_child;
}
check(thread->state == ThreadState::Exited);
}
else if (pid == -1)
@ -33,7 +43,16 @@ Result<u64> sys_waitpid(Registers*, SyscallArgs args)
{
if (options & WNOHANG) return err(EAGAIN);
wait_for_any_child:
kernel_wait(pid);
if (current->interrupted)
{
kdbgln("signal: waitpid interrupted by signal");
if (current->will_invoke_signal_handler()) return err(EINTR);
current->process_pending_signals(regs);
goto wait_for_any_child;
}
check(current->child_being_waited_for.value_or(-1) != -1);
thread = TRY(Result<Thread*>::from_option(Scheduler::find_by_pid(*current->child_being_waited_for), ESRCH));

View File

@ -25,7 +25,7 @@ static bool can_write_segment(u32 flags)
namespace ELFLoader
{
Result<ELFData> load(SharedPtr<VFS::Inode> inode, UserVM* vm)
Result<ELFData> load(SharedPtr<VFS::Inode> inode, AddressSpace* space)
{
Elf64_Ehdr elf_header;
usize nread = TRY(inode->read((u8*)&elf_header, 0, sizeof elf_header));
@ -102,7 +102,7 @@ namespace ELFLoader
if (can_write_segment(program_header.p_flags)) flags |= MMU::ReadWrite;
if (can_execute_segment(program_header.p_flags)) flags &= ~MMU::NoExecute;
if (!TRY(vm->test_and_alloc_region(
if (!TRY(space->test_and_alloc_region(
base_vaddr, get_blocks_from_size(program_header.p_memsz + vaddr_diff, ARCH_PAGE_SIZE), true)))
return err(ENOMEM);

View File

@ -1,6 +1,6 @@
#pragma once
#include "fs/VFS.h"
#include "memory/UserVM.h"
#include "memory/AddressSpace.h"
#include <luna/Types.h>
#define ELFMAG "\177ELF"
@ -54,5 +54,5 @@ struct ELFData
namespace ELFLoader
{
Result<ELFData> load(SharedPtr<VFS::Inode> inode, UserVM* vm);
Result<ELFData> load(SharedPtr<VFS::Inode> inode, AddressSpace* space);
};

View File

@ -27,6 +27,7 @@ namespace Scheduler
g_idle.is_kernel = true;
g_idle.parent = nullptr;
g_idle.name = "[idle]";
g_idle.active_directory = nullptr;
g_idle.ticks_left = 1;
@ -86,6 +87,7 @@ namespace Scheduler
thread->name = name;
thread->is_kernel = true;
thread->active_directory = MMU::kernel_page_directory();
thread->auth = Credentials { .uid = 0, .euid = 0, .suid = 0, .gid = 0, .egid = 0, .sgid = 0 };
@ -133,6 +135,7 @@ namespace Scheduler
thread->state = ThreadState::None;
thread->is_kernel = false;
thread->id = 1;
thread->pgid = 1;
thread->name = name;
thread->auth = Credentials { .uid = 0, .euid = 0, .suid = 0, .gid = 0, .egid = 0, .sgid = 0 };
@ -153,6 +156,11 @@ namespace Scheduler
image->apply(thread);
thread->set_arguments(args.size(), argv, env.size(), envp);
for (int i = 0; i < NSIG; i++)
{
thread->signal_handlers[i] = { .sa_handler = SIG_DFL, .sa_mask = 0, .sa_flags = 0 };
}
kinfoln("Created userspace thread: id %lu with ip %#.16lx and sp %#.16lx (ksp %#lx)", thread->id, thread->ip(),
thread->sp(), thread->kernel_stack.top());
@ -191,8 +199,6 @@ namespace Scheduler
}
}
if (!thread->is_kernel) MMU::delete_userspace_page_directory(thread->directory);
delete thread;
CPU::enable_interrupts();
@ -235,9 +241,9 @@ namespace Scheduler
{
switch_context(old_thread, new_thread, regs);
if (!old_thread->is_kernel) old_thread->fp_data.save();
if (old_thread->is_kernel && MMU::get_page_directory() != MMU::kernel_page_directory())
old_thread->directory = MMU::get_page_directory();
if (new_thread->directory) MMU::switch_page_directory(new_thread->directory);
if (old_thread->state != ThreadState::Idle && MMU::get_page_directory() != MMU::kernel_page_directory())
old_thread->active_directory = MMU::get_page_directory();
if (new_thread->active_directory) MMU::switch_page_directory(new_thread->active_directory);
if (!new_thread->is_kernel)
{
CPU::switch_kernel_stack(new_thread->kernel_stack.top());
@ -259,6 +265,7 @@ namespace Scheduler
Thread* old_thread = g_current;
Thread* new_thread = pick_task();
generic_switch_context(old_thread, new_thread, regs);
if (!is_in_kernel(regs)) new_thread->process_pending_signals(regs);
}
void invoke(Registers* regs)

View File

@ -45,6 +45,19 @@ namespace Scheduler
}
}
template <typename Callback> void for_each_in_process_group(pid_t group, Callback callback)
{
for (Thread* current = g_threads.first().value_or(nullptr); current;
current = g_threads.next(current).value_or(nullptr))
{
if (current->pgid == (u64)group)
{
bool should_continue = callback(current);
if (!should_continue) return;
}
}
}
void dump_state();
bool has_children(Thread* thread);

View File

@ -1,8 +1,12 @@
#include "thread/Thread.h"
#include "Log.h"
#include "arch/CPU.h"
#include "memory/MemoryManager.h"
#include "thread/Scheduler.h"
#include <bits/atfile.h>
#include <bits/open-flags.h>
#include <bits/signal.h>
#include <bits/waitpid.h>
#include <luna/Alloc.h>
#include <luna/Atomic.h>
#include <luna/PathParser.h>
@ -25,6 +29,11 @@ Result<Thread*> new_thread()
return thread;
}
u64 next_thread_id()
{
return g_next_id.load();
}
Result<int> Thread::allocate_fd(int min)
{
if (min < 0 || min >= FD_MAX) return err(EINVAL);
@ -69,8 +78,13 @@ Result<SharedPtr<VFS::Inode>> Thread::resolve_atfile(int dirfd, const String& pa
return VFS::resolve_path(path.chars(), this->auth, descriptor->inode, follow_last_symlink);
}
[[noreturn]] void Thread::exit_and_signal_parent(u8 _status)
[[noreturn]] void Thread::exit_and_signal_parent(int _status)
{
#ifndef MOON_ENABLE_TESTING_FEATURES
if (this->id == 1) fail("the init process exited");
#else
if (this->id == 1) CPU::magic_exit(_status);
#endif
if (is_kernel) state = ThreadState::Dying;
else
{
@ -79,7 +93,9 @@ Result<SharedPtr<VFS::Inode>> Thread::resolve_atfile(int dirfd, const String& pa
return true;
});
if (parent && parent->state == ThreadState::Waiting)
if (parent)
{
if (parent->state == ThreadState::Waiting)
{
auto child = *parent->child_being_waited_for;
if (child == -1 || child == (pid_t)id)
@ -88,6 +104,8 @@ Result<SharedPtr<VFS::Inode>> Thread::resolve_atfile(int dirfd, const String& pa
parent->wake_up();
}
}
else { parent->send_signal(SIGCHLD); }
}
state = ThreadState::Exited;
}
@ -96,6 +114,106 @@ Result<SharedPtr<VFS::Inode>> Thread::resolve_atfile(int dirfd, const String& pa
unreachable();
}
enum class DefaultSignalAction
{
Ignore,
Terminate,
};
// FIXME: Implement coredumps for some signals.
static constexpr DefaultSignalAction default_actions[] = {
DefaultSignalAction::Terminate, // SIGHUP
DefaultSignalAction::Terminate, // SIGINT
DefaultSignalAction::Terminate, // SIGQUIT (dump core)
DefaultSignalAction::Terminate, // SIGILL (dump core)
DefaultSignalAction::Terminate, // SIGTRAP (dump core)
DefaultSignalAction::Terminate, // SIGABRT (dump core)
DefaultSignalAction::Ignore, // SIGCHLD
DefaultSignalAction::Terminate, // SIGFPE (dump core)
DefaultSignalAction::Terminate, // SIGKILL
DefaultSignalAction::Terminate, // SIGSTOP (FIXME: Support stopping and continuing)
DefaultSignalAction::Terminate, // SIGSEGV (dump core)
DefaultSignalAction::Ignore, // SIGCONT (FIXME: Support stopping and continuing)
DefaultSignalAction::Terminate, // SIGPIPE
DefaultSignalAction::Terminate, // SIGALRM
DefaultSignalAction::Terminate, // SIGTERM
DefaultSignalAction::Terminate, // SIGTTIN
DefaultSignalAction::Terminate, // SIGTTOU
};
void Thread::process_pending_signals(Registers* current_regs)
{
interrupted = false;
for (int i = 0; i < NSIG; i++)
{
int signo = i + 1;
if (signo != SIGKILL && signo != SIGSTOP && signal_mask & (1 << i)) continue;
if (pending_signals & (1 << i))
{
pending_signals &= ~(1 << i);
kinfoln("signal: executing signal %d for thread %ld", signo, id);
auto handler = signal_handlers[i];
if (signo != SIGKILL && signo != SIGSTOP && handler.sa_handler == SIG_IGN)
{
kinfoln("signal: ignoring signal (handler=SIG_IGN)");
return;
}
if (handler.sa_handler == SIG_DFL || signo == SIGKILL || signo == SIGSTOP)
{
default_signal:
if (id == 1)
{
kwarnln("signal: init got a signal it has no handler for, ignoring");
return;
}
kinfoln("signal: using default behavior (handler=SIG_DFL)");
auto action = default_actions[i];
switch (action)
{
case DefaultSignalAction::Ignore: return;
case DefaultSignalAction::Terminate: exit_and_signal_parent(signo | _SIGBIT);
default: return;
}
}
// If we fail to deliver the signal (usually because there's not enough space on the stack), execute the
// default action.
if (!deliver_signal(signo, current_regs)) goto default_signal;
return;
}
}
}
bool Thread::will_invoke_signal_handler()
{
for (int i = 0; i < NSIG; i++)
{
if (pending_signals & (1 << i))
{
int signo = i + 1;
if (signo != SIGKILL && signo != SIGSTOP && signal_mask & (1 << i)) continue;
auto handler = signal_handlers[i];
if (handler.sa_handler == SIG_IGN || handler.sa_handler == SIG_DFL) return false;
if (signo == SIGKILL || signo == SIGSTOP) return false;
return true;
}
}
return false;
}
void Thread::send_signal(int signo)
{
check(signo > 0 && signo <= NSIG);
pending_signals |= 1 << (signo - 1);
if (state == ThreadState::Waiting || state == ThreadState::Sleeping)
{
interrupted = true;
wake_up();
}
}
bool FileDescriptor::should_append()
{
return flags & O_APPEND;

View File

@ -2,7 +2,8 @@
#include "arch/MMU.h"
#include "fs/VFS.h"
#include "memory/UserVM.h"
#include "memory/AddressSpace.h"
#include <bits/signal.h>
#include <luna/LinkedList.h>
#include <luna/OwnedPtr.h>
#include <luna/Result.h>
@ -56,6 +57,7 @@ struct Thread : public LinkedListNode<Thread>
Registers regs;
u64 id;
u64 pgid { 0 };
Credentials auth;
@ -70,7 +72,7 @@ struct Thread : public LinkedListNode<Thread>
Stack stack;
Stack kernel_stack;
OwnedPtr<UserVM> vm_allocator;
OwnedPtr<AddressSpace> address_space;
Option<FileDescriptor> fd_table[FD_MAX] = {};
Result<int> allocate_fd(int min);
@ -79,13 +81,19 @@ struct Thread : public LinkedListNode<Thread>
bool follow_last_symlink,
SharedPtr<VFS::Inode>* parent_inode = nullptr);
struct sigaction signal_handlers[NSIG];
sigset_t signal_mask { 0 };
sigset_t pending_signals { 0 };
bool interrupted { false };
FPData fp_data;
ThreadState state = ThreadState::Runnable;
bool is_kernel { true };
bool has_called_exec { false };
u8 status { 0 };
int status { 0 };
mode_t umask { 0 };
@ -97,9 +105,14 @@ struct Thread : public LinkedListNode<Thread>
Thread* parent { nullptr };
Option<pid_t> child_being_waited_for = {};
PageDirectory* directory;
PageDirectory* self_directory() const
{
return address_space->page_directory();
}
[[noreturn]] void exit_and_signal_parent(u8 status);
PageDirectory* active_directory { nullptr };
[[noreturn]] void exit_and_signal_parent(int status);
bool is_idle()
{
@ -123,6 +136,19 @@ struct Thread : public LinkedListNode<Thread>
u64 sp();
void set_return(u64 ret);
u64 return_register();
void process_pending_signals(Registers* current_regs);
bool will_invoke_signal_handler();
bool deliver_signal(int signo, Registers* current_regs);
void sigreturn(Registers* current_regs);
Result<u64> push_mem_on_stack(const u8* mem, usize size);
Result<u64> pop_mem_from_stack(u8* mem, usize size);
void send_signal(int signo);
static void init();
};
@ -133,4 +159,6 @@ bool is_in_kernel(Registers* regs);
Result<Thread*> new_thread();
u64 next_thread_id();
extern LinkedList<Thread> g_threads;

View File

@ -6,24 +6,20 @@
static constexpr usize DEFAULT_USER_STACK_PAGES = 6;
static constexpr usize DEFAULT_USER_STACK_SIZE = DEFAULT_USER_STACK_PAGES * ARCH_PAGE_SIZE;
static constexpr u64 THREAD_STACK_BASE = 0x10000;
static Result<void> create_stacks(Stack& user_stack, Stack& kernel_stack, UserVM* vm)
static Result<void> create_user_stack(Stack& user_stack, AddressSpace* space)
{
const u64 THREAD_STACK_BASE = 0x10000;
if (!TRY(vm->test_and_alloc_region(THREAD_STACK_BASE, DEFAULT_USER_STACK_PAGES, true))) return err(ENOMEM);
TRY(MemoryManager::alloc_at_zeroed(THREAD_STACK_BASE, DEFAULT_USER_STACK_PAGES,
MMU::ReadWrite | MMU::NoExecute | MMU::User));
auto guard = make_scope_guard([&] { MemoryManager::unmap_owned(THREAD_STACK_BASE, 4); });
auto guard = make_scope_guard([] { MemoryManager::unmap_owned(THREAD_STACK_BASE, DEFAULT_USER_STACK_PAGES); });
const u64 kernel_stack_base = TRY(MemoryManager::alloc_for_kernel(4, MMU::ReadWrite | MMU::NoExecute));
if (!TRY(space->test_and_alloc_region(THREAD_STACK_BASE, DEFAULT_USER_STACK_PAGES, true))) return err(ENOMEM);
guard.deactivate();
user_stack = { THREAD_STACK_BASE, DEFAULT_USER_STACK_SIZE };
kernel_stack = { kernel_stack_base, 4 * ARCH_PAGE_SIZE };
return {};
}
@ -32,32 +28,24 @@ Result<OwnedPtr<ThreadImage>> ThreadImage::try_load_from_elf(SharedPtr<VFS::Inod
{
auto image = TRY(make_owned<ThreadImage>());
auto vm_allocator = TRY(UserVM::try_create());
auto address_space = TRY(AddressSpace::try_create());
auto old_directory = MMU::get_page_directory();
auto new_directory = TRY(MMU::create_page_directory_for_userspace());
MMU::switch_page_directory(address_space->page_directory());
MMU::switch_page_directory(new_directory);
auto guard = make_scope_guard([=] { MMU::switch_page_directory(old_directory); });
auto guard = make_scope_guard([=] {
MMU::delete_userspace_page_directory(new_directory);
MMU::switch_page_directory(old_directory);
});
const ELFData data = TRY(ELFLoader::load(inode, vm_allocator.ptr()));
const ELFData data = TRY(ELFLoader::load(inode, address_space.ptr()));
Stack user_stack;
Stack kernel_stack;
TRY(create_stacks(user_stack, kernel_stack, vm_allocator.ptr()));
TRY(create_user_stack(user_stack, address_space.ptr()));
guard.deactivate();
image->m_directory = new_directory;
image->m_kernel_stack = kernel_stack;
image->m_user_stack = user_stack;
image->m_loaded_image_data = data;
image->m_vm_allocator = move(vm_allocator);
image->m_address_space = move(address_space);
image->m_sp = user_stack.top();
return image;
@ -67,20 +55,17 @@ Result<OwnedPtr<ThreadImage>> ThreadImage::clone_from_thread(Thread* parent)
{
auto image = TRY(make_owned<ThreadImage>());
auto vm_allocator = TRY(parent->vm_allocator->clone());
auto new_directory = TRY(MMU::clone_userspace_page_directory(parent->directory));
auto address_space = TRY(parent->address_space->clone());
const ELFData data = { .entry = parent->ip() };
const u64 kernel_stack_base = TRY(MemoryManager::alloc_for_kernel(4, MMU::ReadWrite | MMU::NoExecute));
Stack kernel_stack { kernel_stack_base, 4 * ARCH_PAGE_SIZE };
image->m_directory = new_directory;
image->m_kernel_stack = kernel_stack;
image->m_user_stack = parent->stack;
image->m_loaded_image_data = data;
image->m_vm_allocator = move(vm_allocator);
image->m_address_space = move(address_space);
image->m_sp = parent->sp();
return image;
@ -120,11 +105,11 @@ void ThreadImage::apply(Thread* thread)
thread->set_ip(m_loaded_image_data.entry);
thread->kernel_stack = m_kernel_stack;
if (m_kernel_stack.bottom()) thread->kernel_stack = m_kernel_stack;
thread->stack = m_user_stack;
thread->set_sp(align_down<16>(m_sp));
thread->directory = m_directory;
thread->active_directory = m_address_space->page_directory();
thread->vm_allocator = move(m_vm_allocator);
thread->address_space = move(m_address_space);
}

View File

@ -4,7 +4,7 @@
#include "arch/CPU.h"
#include "arch/MMU.h"
#include "fs/VFS.h"
#include "memory/UserVM.h"
#include "memory/AddressSpace.h"
#include "thread/Thread.h"
#include <luna/LinkedList.h>
#include <luna/OwnedPtr.h>
@ -28,8 +28,7 @@ class ThreadImage
void apply(Thread* thread);
private:
OwnedPtr<UserVM> m_vm_allocator;
PageDirectory* m_directory { nullptr };
OwnedPtr<AddressSpace> m_address_space;
Stack m_user_stack;
Stack m_kernel_stack;
ELFData m_loaded_image_data;

View File

@ -101,6 +101,10 @@ namespace TextConsole
if (should_scroll()) scroll();
break;
}
case L'\t': {
wprint(L" ");
break;
}
case L'\r': g_x_position = 0; break;
case L'\b':
if (g_x_position != 0)
@ -207,4 +211,14 @@ namespace TextConsole
va_end(ap);
return rc;
}
u16 rows()
{
return (u16)Framebuffer::height() / (u16)FONT_HEIGHT;
}
u16 cols()
{
return (u16)Framebuffer::width() / (u16)FONT_WIDTH;
}
}

View File

@ -19,4 +19,7 @@ namespace TextConsole
Result<void> println(const char* str);
void wprintln(const wchar_t* str);
Result<usize> printf(const char* format, ...) _format(1, 2);
u16 rows();
u16 cols();
}

View File

@ -21,6 +21,8 @@ set(SOURCES
src/grp.cpp
src/locale.cpp
src/scanf.cpp
src/signal.cpp
src/termios.cpp
src/sys/stat.cpp
src/sys/mman.cpp
src/sys/wait.cpp
@ -36,6 +38,7 @@ if(${LUNA_ARCH} STREQUAL "x86_64")
${SOURCES}
src/arch/x86_64/syscall.S
src/arch/x86_64/setjmp.S
src/arch/x86_64/sigreturn.S
)
endif()

View File

@ -3,7 +3,8 @@
#ifndef _BITS_ATTRS_H
#define _BITS_ATTRS_H
#if !defined(_STDLIB_H) && !defined(_STRING_H) && !defined(_ASSERT_H) && !defined(_SETJMP_H) && !defined(_SYS_TIME_H)
#if !defined(_STDLIB_H) && !defined(_UNISTD_H) && !defined(_STRING_H) && !defined(_ASSERT_H) && !defined(_SETJMP_H) && \
!defined(_SYS_TIME_H)
#error "Never include bits/attrs.h directly; use one of the standard library headers."
#endif

View File

@ -3,7 +3,7 @@
#ifndef _BITS_FIXED_SIZE_TYPES_H
#define _BITS_FIXED_SIZE_TYPES_H
#if !defined(_SYS_TYPES_H) && !defined(_BITS_SETJMP_TYPES_H) && !defined(_BITS_MAKEDEV_H)
#if !defined(_SYS_TYPES_H) && !defined(_BITS_SETJMP_TYPES_H) && !defined(_BITS_MAKEDEV_H) && !defined(_BITS_TERMIOS_H)
#error "Never include bits/fixed-size-types.h, use the standard <stdint.h> header instead."
#endif

View File

@ -0,0 +1,60 @@
/* bits/signal.h: Signal-related definitions. */
#ifndef _BITS_SIGNAL_H
#define _BITS_SIGNAL_H
typedef void (*__simple_sighandler_t)(int);
#define SIG_IGN (__simple_sighandler_t)(-1)
#define SIG_DFL (__simple_sighandler_t)(-2)
typedef int sigset_t;
struct sigaction
{
__simple_sighandler_t sa_handler;
sigset_t sa_mask;
int sa_flags;
#ifdef __cplusplus
void* __sa_sigreturn = nullptr;
#else
void* __sa_sigreturn;
#endif
};
// Constants for sigaction's sa_flags field.
#define SA_NODEFER (1 << 0)
#define SA_RESETHAND (1 << 1)
// Constants for the 'how' parameter in sigprocmask().
#define SIG_BLOCK 0
#define SIG_UNBLOCK 1
#define SIG_SETMASK 2
// The signals with explicit numbers have portable signal numbers.
enum __signals
{
SIGHUP = 1,
SIGINT = 2,
SIGQUIT = 3,
SIGILL = 4,
SIGTRAP = 5,
SIGABRT = 6,
SIGCHLD,
SIGFPE = 8,
SIGKILL = 9,
SIGSTOP,
SIGSEGV = 11,
SIGCONT,
SIGPIPE = 13,
SIGALRM = 14,
SIGTERM = 15,
SIGTTIN = 16,
SIGTTOU = 17,
// FIXME: Add the remaining signals.
__NSIG,
};
#define NSIG (__NSIG - 1)
#endif

View File

@ -3,19 +3,47 @@
#ifndef _BITS_TERMIOS_H
#define _BITS_TERMIOS_H
#include <bits/fixed-size-types.h>
typedef long tcflag_t;
typedef char cc_t;
#define NCCS 4
#define VEOF 0
#define VERASE 1
#define VINTR 2
#define VQUIT 3
// VERY incomplete termios structure.
struct termios
{
tcflag_t c_lflag;
cc_t c_cc[NCCS];
};
struct winsize
{
__u16_t ws_row;
__u16_t ws_col;
__u16_t ws_xpixel;
__u16_t ws_ypixel;
};
// Values for c_lflag.
#define ECHO 1
#define TOSTOP 2
#define ECHOCTL 4
#define ISIG 8
#define ICANON 16
#define ECHOE 32
#define NOFLSH 64
// termios ioctl() requests.
#define TCGETS 0
#define TCSETS 1
#define TIOCSPGRP 2
#define TIOCGPGRP 3
#define TIOCGWINSZ 4
#endif

View File

@ -3,6 +3,8 @@
#ifndef _BITS_WAITPID_H
#define _BITS_WAITPID_H
#define _SIGBIT 0x100
#define WNOHANG 1
#endif

View File

@ -49,6 +49,20 @@
#define PRIuFAST64 __PRI64_PREFIX "u"
#define PRIuMAX __PRI64_PREFIX "u"
#define PRIuPTR __PRI64_PREFIX "u"
#define PRIo8 "o"
#define PRIo16 "o"
#define PRIo32 "o"
#define PRIo64 __PRI64_PREFIX "o"
#define PRIoLEAST8 "o"
#define PRIoLEAST16 "o"
#define PRIoLEAST32 "o"
#define PRIoLEAST64 __PRI64_PREFIX "o"
#define PRIoFAST8 "o"
#define PRIoFAST16 "o"
#define PRIoFAST32 "o"
#define PRIoFAST64 __PRI64_PREFIX "o"
#define PRIoMAX __PRI64_PREFIX "o"
#define PRIoPTR __PRI64_PREFIX "o"
#define PRIx8 "x"
#define PRIx16 "x"
#define PRIx32 "x"

0
libc/include/math.h Normal file
View File

12
libc/include/memory.h Normal file
View File

@ -0,0 +1,12 @@
/* memory.h: Legacy header for string/memory manipulation functions. */
#ifndef _MEMORY_H
#define _MEMORY_H
#warning "memory.h is a legacy header, you should use string.h in new code"
#ifndef _STRING_H
#include <string.h>
#endif
#endif

View File

@ -3,6 +3,53 @@
#ifndef _SIGNAL_H
#define _SIGNAL_H
#include <bits/signal.h>
#include <sys/types.h>
typedef int sig_atomic_t;
#define SIG_ERR (__simple_sighandler_t)(-3)
#ifdef __cplusplus
extern "C"
{
#endif
#pragma GCC push_options
#pragma GCC diagnostic ignored "-Wshadow"
/* Examine/change the current thread's signal disposition for a specific signal. */
int sigaction(int signo, const struct sigaction* act, struct sigaction* oldact);
#pragma GCC pop_options
/* Change the current thread's signal disposition for a specific signal. */
__simple_sighandler_t signal(int signo, __simple_sighandler_t handler);
/* Send a signal to a specific process. */
int kill(pid_t pid, int signo);
/* Send a signal to the current thread. */
int raise(int signo);
/* Modify the current thread's signal mask. */
int sigprocmask(int how, const sigset_t* set, sigset_t* oldset);
/* Clear all signals from set. */
int sigemptyset(sigset_t* set);
/* Add all signals to set. */
int sigfillset(sigset_t* set);
/* Add a specific signal to set. */
int sigaddset(sigset_t* set, int signo);
/* Remove a specific signal from set. */
int sigdelset(sigset_t* set, int signo);
/* Check if a signal is in set.*/
int sigismember(const sigset_t* set, int signo);
#ifdef __cplusplus
}
#endif
#endif

View File

@ -10,11 +10,23 @@
#define __need_NULL
#include <stddef.h>
#define FOPEN_MAX 64 // Make sure this value matches FD_MAX in the kernel source.
typedef struct
{
int _fd;
int _err;
int _eof;
int _fd; // The underlying file descriptor.
int _err; // The error status flag.
int _eof; // The end-of-file status flag.
struct
{
size_t capacity; // The buffer's total capacity.
size_t size; // The buffer's used size.
size_t index; // The read index into the buffer.
char* buffer; // The memory used for the buffer.
int status; // The buffer status flags.
int mode; // The buffering mode.
} _buf;
int _flags; // The file access mode with which the file was opened.
} FILE;
#define EOF -1
@ -26,11 +38,15 @@ extern FILE* stderr;
#define stdout stdout
#define stderr stderr
#define BUFSIZ 1024
#define BUFSIZ 4096
#define FILENAME_MAX \
1024 // As Luna does not impose a limit on this, this is the recommended size for character arrays holding a file
// name.
#define _IONBF 0
#define _IOLBF 1
#define _IOFBF 2
#ifdef __cplusplus
extern "C"
{
@ -116,8 +132,6 @@ extern "C"
/* Clear the error and end-of-file indicators in stream. */
void clearerr(FILE* stream);
void setbuf(FILE*, char*);
/* Write formatted output to a file. */
int fprintf(FILE* stream, const char* format, ...);
@ -172,6 +186,18 @@ extern "C"
/* Create a unique temporary file. */
FILE* tmpfile(void);
/* Change a file's buffering mode and internal buffer. */
int setvbuf(FILE* stream, char* buf, int mode, size_t size);
/* Change a file's internal buffer. */
void setbuf(FILE* stream, char* buf);
/* Change a file's internal buffer. */
void setbuffer(FILE* stream, char* buf, size_t size);
/* Change a file's buffering mode to line buffered. */
void setlinebuf(FILE* stream);
#ifdef __cplusplus
}
#endif

Some files were not shown because too many files have changed in this diff Show More