kernel+apps+base+tools: Use Ext2 for the root partition file system
All checks were successful
continuous-integration/drone/push Build is passing

init is now split into two parts: preinit, which lives in the initrd and prepares the root file system for init,
and the actual /usr/bin/init, which lives in the root partition and starts services and reaps zombies.

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

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

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

The system is slower now, but that's to expect since the Ext2 code doesn't use caching and the ATA code still uses PIO.
This commit is contained in:
apio 2023-07-10 13:05:06 +02:00
parent 40413eee18
commit 16b0531d42
Signed by: apio
GPG Key ID: B8A7D06E42258954
23 changed files with 160 additions and 61 deletions

3
.gitignore vendored
View File

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

View File

@ -6,9 +6,10 @@ 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.
- 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.
- Designed to be [portable](kernel/src/arch), no need to be restricted to x86_64.
- Designed around [UTF-8](libluna/include/luna/Utf8.h).

View File

@ -4,9 +4,15 @@ 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)

View File

@ -16,7 +16,7 @@
#include <sys/sysmacros.h>
#include <unistd.h>
FILE* g_init_log;
FILE* g_init_log = nullptr;
struct Service
{
@ -33,6 +33,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 +92,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 +121,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 +144,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 {};
}
@ -149,8 +158,8 @@ static Result<void> load_service(const os::Path& path)
{
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 +170,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 +224,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 +263,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 +279,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", "devfs") < 0) exit(255);
if (chmod("/tmp", 01777) < 0) exit(255);
}
int main()
@ -290,17 +299,17 @@ 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("/tmp/init.log", "w+");
fcntl(fileno(g_init_log), F_SETFD, FD_CLOEXEC);
set_hostname();
@ -319,12 +328,11 @@ 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));
do_log("[init] service %s exited with status %d\n", service.name.chars(), WEXITSTATUS(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);
}

86
apps/preinit.cpp Normal file
View File

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

1
base/bin Symbolic link
View File

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

3
base/etc/init/00-motd Normal file
View File

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

3
base/etc/init/01-selene Normal file
View File

@ -0,0 +1,3 @@
Name=selene
Script=/etc/startup/selene-home.sh
Wait=true

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -45,9 +45,10 @@ void reap_thread()
mark_critical(InitRD::populate_vfs(), "Failed to load files from the initial ramdisk");
mark_critical(DeviceRegistry::init(), "Failed to register initial devices");
auto init = mark_critical(VFS::resolve_path("/bin/init", Credentials {}), "Can't find init in the initial ramfs!");
auto init =
mark_critical(VFS::resolve_path("/bin/preinit", Credentials {}), "Can't find init in the initial ramfs!");
auto init_thread =
mark_critical(Scheduler::new_userspace_thread(init, "/bin/init"), "Failed to create PID 1 process for init");
mark_critical(Scheduler::new_userspace_thread(init, "/bin/preinit"), "Failed to create PID 1 process for init");
auto reap = mark_critical(Scheduler::new_kernel_thread(reap_thread, "[reap]"),
"Failed to create the process reaper kernel thread");

View File

@ -1,6 +1,6 @@
{
"diskguid": "00000000-0000-0000-0000-000000000000",
"disksize": 20,
"disksize": 32,
"align": 1024,
"iso9660": true,
"config": "initrd/sys/config",
@ -12,7 +12,7 @@
"partitions": [
{
"type": "boot",
"size": 16
"size": 8
},
{
"type": "ext2",

View File

@ -9,7 +9,7 @@ function(luna_test 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 test os)
install(TARGETS ${APP_NAME} DESTINATION ${LUNA_ROOT}/initrd/bin/tests)
install(TARGETS ${APP_NAME} DESTINATION ${LUNA_BASE}/usr/bin/tests)
endfunction()
if(BUILD_TESTS)

View File

@ -17,14 +17,17 @@ then
exit 1
fi
chown -R root:root initrd
chown -R root:root base
cmake --install $LUNA_BUILD_DIR
chmod 400 initrd/boot/moon
chmod a+s initrd/bin/su
chmod a+s base/usr/bin/su
mkdir -p initrd/home/selene
chown 1000:1000 initrd/home/selene
mkdir -p base/home/selene
mkdir -p initrd/dev
mkdir -p base/dev
mkdir -p base/mnt
mkdir -p base/tmp
rm -f base/bin
ln -s usr/bin base/bin

View File

@ -7,6 +7,6 @@ cd $LUNA_ROOT
fakeroot -u -s $LUNA_ROOT/.fakeroot -- tools/install.sh
genext2fs -d base -B 4096 -b 1024 -L luna-rootfs -U -N 1024 build/ext2fs.bin
fakeroot -u -i $LUNA_ROOT/.fakeroot -- genext2fs -d base -B 4096 -b 1024 -L luna-rootfs -N 1024 build/ext2fs.bin
fakeroot -u -i $LUNA_ROOT/.fakeroot mkbootimg luna.json Luna.iso
mkbootimg luna.json Luna.iso