Compare commits
112 Commits
3d157b760c
...
eeb9e16a74
Author | SHA1 | Date | |
---|---|---|---|
eeb9e16a74 | |||
358493a7bc | |||
5110d740b8 | |||
759fb4fe0e | |||
098109f16b | |||
9ef09cfc88 | |||
c17e1a5802 | |||
085d2895e8 | |||
77022abafd | |||
19b4aa9f81 | |||
420270ebd4 | |||
d60ad184f1 | |||
a3ed950be8 | |||
cfb0ead2d9 | |||
5458286309 | |||
c72c6312d4 | |||
7a4d3ba495 | |||
16b385fc7b | |||
4439ef8ec6 | |||
ff9e01641e | |||
36a74fd8d6 | |||
310b325af8 | |||
0d924f89d3 | |||
de7e58c274 | |||
c24a261233 | |||
edeb420d0d | |||
cd6bf745a7 | |||
bcfee628cb | |||
0d41e1f7b6 | |||
f9003d7a58 | |||
89786d8be2 | |||
de6fe7f7c2 | |||
efd5bae7a5 | |||
78ea5dc352 | |||
6c3ab3b27d | |||
95cce6d592 | |||
192621eac5 | |||
acf4fef6f5 | |||
546d900454 | |||
2951d6d112 | |||
5f698b4774 | |||
f629e17ff4 | |||
1f6a0db188 | |||
81e1fdf81e | |||
9f45026cc2 | |||
71ff763dd9 | |||
b64093dee5 | |||
d27ffce5db | |||
1091798195 | |||
9cf35f761f | |||
a51fb6a428 | |||
69f9701097 | |||
7328cfe734 | |||
6546f490b6 | |||
f7e8fd9cb8 | |||
0fed45d1c6 | |||
82411789e8 | |||
86d14e0d0e | |||
237184a8bf | |||
e0b5acb2ab | |||
66365e15a7 | |||
4a5947e10e | |||
fe9827bbeb | |||
3df40beaf2 | |||
8066e8f1d8 | |||
015419b8f5 | |||
60d68b74e1 | |||
cde467ee46 | |||
fc3fdc2b87 | |||
bdcb690a7a | |||
15d6aae701 | |||
15199a2366 | |||
56f3d26969 | |||
16b0531d42 | |||
40413eee18 | |||
e3552d9df0 | |||
a1b92fcc3f | |||
0b488c1232 | |||
b920ffee42 | |||
503dc72686 | |||
7908c5a63e | |||
ae0cd155c3 | |||
c599251d2a | |||
5e564e9ae3 | |||
5c9503ac71 | |||
6d9ba8deb4 | |||
af26dce038 | |||
3b1219ecf2 | |||
bd757d204e | |||
498e20561f | |||
d363d5e915 | |||
f0a7098470 | |||
6db0a2649c | |||
dd914b16d1 | |||
f8dc7c62e2 | |||
21d093b1fa | |||
e9e7b22323 | |||
5f4103251a | |||
491416ddaf | |||
da689dd1a7 | |||
41f578aa18 | |||
34e1ef36b1 | |||
a62265b504 | |||
d7486326bf | |||
4fe6c506ec | |||
77686b26f8 | |||
2aa7056e11 | |||
a9460469d9 | |||
707f64acb5 | |||
ddf63471a8 | |||
3b6f5b28fc | |||
fdf2bb2501 |
@ -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
3
.gitignore
vendored
@ -4,7 +4,6 @@ build/
|
||||
initrd/boot/moon
|
||||
env-local.sh
|
||||
initrd/bin/**
|
||||
initrd/tests/**
|
||||
base/
|
||||
base/usr/**
|
||||
.fakeroot
|
||||
kernel/config.cmake
|
||||
|
@ -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)
|
||||
|
54
README.md
54
README.md
@ -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).
|
||||
|
@ -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
27
apps/buffer-test.cpp
Normal 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);
|
||||
}
|
@ -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
74
apps/cp.cpp
Normal 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
166
apps/gol.cpp
Normal 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;
|
||||
}
|
@ -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,12 +167,18 @@ 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",
|
||||
line.chars());
|
||||
do_log("[init] 'Command' cannot be specified after 'Script' has already been set! (%s)\n",
|
||||
line.chars());
|
||||
return {};
|
||||
}
|
||||
service.command = move(parts[1]);
|
||||
@ -161,8 +189,8 @@ 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",
|
||||
line.chars());
|
||||
do_log("[init] 'Script' cannot be specified after 'Command' has already been set! (%s)\n",
|
||||
line.chars());
|
||||
return {};
|
||||
}
|
||||
service.command = TRY(String::format("/bin/sh -- %s"_sv, parts[1].chars()));
|
||||
@ -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
23
apps/kill.cpp
Normal 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;
|
||||
}
|
@ -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: ");
|
||||
|
@ -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
86
apps/preinit.cpp
Normal 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;
|
||||
}
|
124
apps/sh.cpp
124
apps/sh.cpp
@ -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
23
apps/signal-test.cpp
Normal 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;
|
||||
}
|
45
apps/su.cpp
45
apps/su.cpp
@ -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);
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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());
|
||||
|
4
base/etc/init/00-home
Normal file
4
base/etc/init/00-home
Normal 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
4
base/etc/init/01-motd
Normal 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
4
base/etc/init/99-login
Normal file
@ -0,0 +1,4 @@
|
||||
Name=login
|
||||
Description=Start the command-line login program.
|
||||
Command=/usr/bin/login
|
||||
Restart=true
|
2
base/etc/startup/mount-home.sh
Normal file
2
base/etc/startup/mount-home.sh
Normal file
@ -0,0 +1,2 @@
|
||||
mount -t tmpfs tmpfs /home/selene
|
||||
chown selene:selene /home/selene
|
@ -1,3 +0,0 @@
|
||||
Name=tmpfs
|
||||
Script=/sbin/mount-tmpfs
|
||||
Wait=true
|
@ -1,3 +0,0 @@
|
||||
Name=motd
|
||||
Command=/bin/cat /etc/motd
|
||||
Wait=true
|
@ -1,3 +0,0 @@
|
||||
Name=login
|
||||
Command=/bin/login
|
||||
Restart=true
|
2
initrd/sbin/mount-ext2fs
Normal file
2
initrd/sbin/mount-ext2fs
Normal file
@ -0,0 +1,2 @@
|
||||
mkdir /mnt
|
||||
mount -t ext2 /dev/cd0p2 /mnt
|
@ -1,5 +0,0 @@
|
||||
#!/bin/sh
|
||||
|
||||
mkdir -p /tmp
|
||||
mount -t tmpfs /tmp
|
||||
chmod 1777 /tmp
|
@ -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)
|
||||
|
@ -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)
|
||||
|
@ -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)
|
||||
|
@ -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();
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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);
|
||||
|
||||
u64 cr2;
|
||||
asm volatile("mov %%cr2, %0" : "=r"(cr2));
|
||||
kerrorln("Page fault at RIP %lx while accessing %lx!", regs->rip, cr2);
|
||||
|
||||
decode_page_fault_error_code(regs->error);
|
||||
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))
|
||||
{
|
||||
// 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);
|
||||
Scheduler::current()->send_signal(signo);
|
||||
Scheduler::current()->process_pending_signals(regs);
|
||||
return;
|
||||
}
|
||||
|
||||
CPU::efficient_halt();
|
||||
}
|
||||
|
||||
[[noreturn]] void handle_general_protection_fault(Registers* regs)
|
||||
void handle_page_fault(Registers* regs)
|
||||
{
|
||||
CPU::disable_interrupts();
|
||||
u64 cr2;
|
||||
asm volatile("mov %%cr2, %0" : "=r"(cr2));
|
||||
kerrorln("Page fault while accessing %lx!", cr2);
|
||||
|
||||
decode_page_fault_error_code(regs->error);
|
||||
|
||||
handle_cpu_exception(SIGSEGV, nullptr, regs);
|
||||
}
|
||||
|
||||
void handle_general_protection_fault(Registers* regs)
|
||||
{
|
||||
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
|
||||
|
@ -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;
|
||||
|
@ -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];
|
||||
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;
|
||||
}
|
||||
|
||||
if (is_shifted(state)) return shifted_key_table[scancode];
|
||||
return key_table[scancode];
|
||||
}
|
||||
|
||||
int modifiers()
|
||||
{
|
||||
return g_modifiers;
|
||||
}
|
||||
}
|
||||
|
@ -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(®s, 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(®s, 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, ®s, 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(®s, 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(®s, 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, ®s, sizeof(regs));
|
||||
}
|
||||
|
@ -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();
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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);
|
||||
|
@ -3,4 +3,12 @@
|
||||
#define MOON_VERSION "@CMAKE_PROJECT_VERSION@"
|
||||
#define MOON_VERSION_MAJOR "@CMAKE_PROJECT_VERSION_MAJOR@"
|
||||
#define MOON_VERSION_MINOR "@CMAKE_PROJECT_VERSION_MINOR@"
|
||||
#define MOON_VERSION_PATCH "@CMAKE_PROJECT_VERSION_PATCH@"
|
||||
#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__
|
||||
|
@ -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;
|
||||
|
||||
|
@ -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 {};
|
||||
}
|
||||
|
||||
|
@ -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;
|
||||
|
||||
|
@ -1,104 +1,195 @@
|
||||
#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')
|
||||
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 (g_temp_input.try_pop().has_value())
|
||||
if (key == m_settings.c_cc[VERASE])
|
||||
{
|
||||
if (g_echo) TextConsole::putwchar(L'\b');
|
||||
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;
|
||||
}
|
||||
|
||||
if ((m_settings.c_lflag & ECHOE)) return;
|
||||
else
|
||||
is_special_character = true;
|
||||
}
|
||||
|
||||
return;
|
||||
if (key == m_settings.c_cc[VEOF])
|
||||
{
|
||||
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 (m_settings.c_lflag & ISIG)
|
||||
{
|
||||
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 == m_settings.c_cc[VQUIT])
|
||||
{
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Ctrl+D
|
||||
if (key == 'd' && (Keyboard::modifiers() & Keyboard::LeftControl))
|
||||
if (!is_special_character)
|
||||
{
|
||||
if (g_temp_input.size() == 0) g_eof = true;
|
||||
return;
|
||||
if (is_canonical())
|
||||
{
|
||||
m_line_buffer.try_append((u8)key);
|
||||
|
||||
if (key == '\n')
|
||||
{
|
||||
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 (key == 'e' && (Keyboard::modifiers() & (Keyboard::LeftAlt | Keyboard::LeftControl)))
|
||||
if (!(m_settings.c_lflag & ECHO)) return;
|
||||
|
||||
if (_iscntrl(key))
|
||||
{
|
||||
Scheduler::dump_state();
|
||||
return;
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
if (key == 'm' && (Keyboard::modifiers() & (Keyboard::LeftAlt | Keyboard::LeftControl)))
|
||||
{
|
||||
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 == 'h' && (Keyboard::modifiers() & (Keyboard::LeftAlt | Keyboard::LeftControl)))
|
||||
{
|
||||
dump_heap_usage();
|
||||
return;
|
||||
}
|
||||
|
||||
g_temp_input.try_append((u8)key);
|
||||
|
||||
if (key == '\n')
|
||||
{
|
||||
g_console_input.append_data(g_temp_input.data(), g_temp_input.size());
|
||||
g_temp_input.clear();
|
||||
}
|
||||
|
||||
if (!g_echo) return;
|
||||
TextConsole::putchar(key);
|
||||
else
|
||||
TextConsole::putchar(key);
|
||||
}
|
||||
|
||||
Result<u64> ConsoleDevice::ioctl(int request, void* arg)
|
||||
@ -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);
|
||||
|
@ -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;
|
||||
};
|
||||
|
@ -14,6 +14,11 @@ class Device
|
||||
return err(ENOTTY);
|
||||
}
|
||||
|
||||
virtual Result<u64> isatty() const
|
||||
{
|
||||
return err(ENOTTY);
|
||||
}
|
||||
|
||||
virtual usize size() const
|
||||
{
|
||||
return 0;
|
||||
|
@ -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 {};
|
||||
}
|
||||
|
@ -15,6 +15,7 @@ namespace DeviceRegistry
|
||||
Framebuffer = 3,
|
||||
Disk = 4,
|
||||
DiskPartition = 5,
|
||||
Serial = 6,
|
||||
};
|
||||
|
||||
Result<SharedPtr<Device>> fetch_special_device(u32 major, u32 minor);
|
||||
|
14
kernel/src/fs/devices/UARTDevice.cpp
Normal file
14
kernel/src/fs/devices/UARTDevice.cpp
Normal 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;
|
||||
}
|
28
kernel/src/fs/devices/UARTDevice.h
Normal file
28
kernel/src/fs/devices/UARTDevice.h
Normal 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;
|
||||
};
|
134
kernel/src/fs/ext2/FileSystem.cpp
Normal file
134
kernel/src/fs/ext2/FileSystem.cpp
Normal 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, "..");
|
||||
}
|
||||
}
|
197
kernel/src/fs/ext2/FileSystem.h
Normal file
197
kernel/src/fs/ext2/FileSystem.h
Normal 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;
|
||||
};
|
||||
}
|
205
kernel/src/fs/ext2/Inode.cpp
Normal file
205
kernel/src/fs/ext2/Inode.cpp
Normal 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
156
kernel/src/fs/ext2/Inode.h
Normal 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;
|
||||
};
|
||||
}
|
@ -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;
|
||||
|
@ -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
|
||||
|
@ -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();
|
||||
|
@ -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");
|
||||
|
@ -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);
|
||||
}
|
@ -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;
|
||||
};
|
@ -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)
|
||||
|
@ -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);
|
||||
|
||||
|
@ -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();
|
||||
}
|
||||
|
@ -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, ¤t->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(¤t->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);
|
||||
|
@ -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];
|
||||
|
@ -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];
|
||||
|
@ -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;
|
||||
|
@ -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);
|
||||
|
||||
|
@ -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
96
kernel/src/sys/signal.cpp
Normal 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, ¤t->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, ¤t->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;
|
||||
}
|
@ -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;
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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));
|
||||
|
@ -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);
|
||||
|
||||
|
@ -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);
|
||||
};
|
||||
|
@ -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)
|
||||
|
@ -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);
|
||||
|
@ -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,14 +93,18 @@ Result<SharedPtr<VFS::Inode>> Thread::resolve_atfile(int dirfd, const String& pa
|
||||
return true;
|
||||
});
|
||||
|
||||
if (parent && parent->state == ThreadState::Waiting)
|
||||
if (parent)
|
||||
{
|
||||
auto child = *parent->child_being_waited_for;
|
||||
if (child == -1 || child == (pid_t)id)
|
||||
if (parent->state == ThreadState::Waiting)
|
||||
{
|
||||
parent->child_being_waited_for = (pid_t)id;
|
||||
parent->wake_up();
|
||||
auto child = *parent->child_being_waited_for;
|
||||
if (child == -1 || child == (pid_t)id)
|
||||
{
|
||||
parent->child_being_waited_for = (pid_t)id;
|
||||
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;
|
||||
|
@ -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;
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -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();
|
||||
}
|
||||
|
@ -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()
|
||||
|
||||
|
@ -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
|
||||
|
||||
|
@ -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
|
||||
|
||||
|
60
libc/include/bits/signal.h
Normal file
60
libc/include/bits/signal.h
Normal 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
|
@ -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
|
||||
|
@ -3,6 +3,8 @@
|
||||
#ifndef _BITS_WAITPID_H
|
||||
#define _BITS_WAITPID_H
|
||||
|
||||
#define _SIGBIT 0x100
|
||||
|
||||
#define WNOHANG 1
|
||||
|
||||
#endif
|
||||
|
@ -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
0
libc/include/math.h
Normal file
12
libc/include/memory.h
Normal file
12
libc/include/memory.h
Normal 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
|
@ -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
|
||||
|
@ -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
Loading…
x
Reference in New Issue
Block a user