mirror of
https://github.com/SerenityOS/serenity
synced 2026-05-09 08:32:04 +02:00
The VFSRootContext class, as its name suggests, holds a context for a root directory with its mount table and the root custody/inode in the same class. The idea is derived from the Linux mount namespace mechanism. It mimicks the concept of the ProcessList object, but it is adjusted for a root directory tree context. In contrast to the ProcessList concept, processes that share the default VFSRootContext can't see other VFSRootContext related properties such as as the mount table and root custody/inode. To accommodate to this change progressively, we internally create 2 main VFS root contexts for now - one for kernel processes (as they don't need to care about VFS root contexts for the most part), and another for all userspace programs. This separation allows us to continue pretending for userspace that everything is "normal" as it is used to be, until we introduce proper interfaces in the mount-related syscalls as well as in the SysFS. We make VFSRootContext objects being listed, as another preparation before we could expose interfaces to userspace. As a result, the PowerStateSwitchTask now iterates on all contexts and tear them down one by one.
224 lines
8.7 KiB
C++
224 lines
8.7 KiB
C++
/*
|
|
* Copyright (c) 2023, kleines Filmröllchen <filmroellchen@serenityos.org>
|
|
*
|
|
* SPDX-License-Identifier: BSD-2-Clause
|
|
*/
|
|
|
|
#include <AK/Platform.h>
|
|
#if ARCH(X86_64)
|
|
# include <Kernel/Arch/x86_64/I8042Reboot.h>
|
|
# include <Kernel/Arch/x86_64/Shutdown.h>
|
|
#elif ARCH(AARCH64)
|
|
# include <Kernel/Arch/aarch64/RPi/Watchdog.h>
|
|
#endif
|
|
#include <AK/StringView.h>
|
|
#include <Kernel/Arch/PowerState.h>
|
|
#include <Kernel/Devices/TTY/VirtualConsole.h>
|
|
#include <Kernel/FileSystem/FileSystem.h>
|
|
#include <Kernel/FileSystem/VirtualFileSystem.h>
|
|
#include <Kernel/Firmware/ACPI/Parser.h>
|
|
#include <Kernel/Library/Panic.h>
|
|
#include <Kernel/Sections.h>
|
|
#include <Kernel/Tasks/FinalizerTask.h>
|
|
#include <Kernel/Tasks/PowerStateSwitchTask.h>
|
|
#include <Kernel/Tasks/Process.h>
|
|
#include <Kernel/Tasks/Scheduler.h>
|
|
|
|
namespace Kernel {
|
|
|
|
Thread* g_power_state_switch_task;
|
|
bool g_in_system_shutdown { false };
|
|
|
|
void PowerStateSwitchTask::power_state_switch_task(void* raw_entry_data)
|
|
{
|
|
Thread::current()->set_priority(THREAD_PRIORITY_HIGH);
|
|
auto entry_data = bit_cast<PowerStateCommand>(raw_entry_data);
|
|
switch (entry_data) {
|
|
case PowerStateCommand::Shutdown:
|
|
MUST(PowerStateSwitchTask::perform_shutdown(DoReboot::No));
|
|
break;
|
|
case PowerStateCommand::Reboot:
|
|
MUST(PowerStateSwitchTask::perform_shutdown(DoReboot::Yes));
|
|
break;
|
|
default:
|
|
PANIC("Unknown power state command: {}", to_underlying(entry_data));
|
|
}
|
|
|
|
// Although common, the system may not halt through this task.
|
|
// Clear the power state switch task so that it can be spawned again.
|
|
g_power_state_switch_task = nullptr;
|
|
}
|
|
|
|
void PowerStateSwitchTask::spawn(PowerStateCommand command)
|
|
{
|
|
VERIFY(g_power_state_switch_task == nullptr);
|
|
auto [_, power_state_switch_task_thread] = MUST(Process::create_kernel_process(
|
|
"Power State Switch Task"sv, power_state_switch_task, bit_cast<void*>(command)));
|
|
g_power_state_switch_task = move(power_state_switch_task_thread);
|
|
}
|
|
|
|
static ErrorOr<void> unmount_mounts_on_vfs_root_context(VFSRootContext& vfs_root_context)
|
|
{
|
|
// NOTE: We are going to tear down the entire VFS root context from its mounts.
|
|
// To do this properly, we swap out the original root custody with the empty
|
|
// root custody for vfs root context of kernel processes.
|
|
vfs_root_context.root_custody().with([](auto& custody) {
|
|
custody = VFSRootContext::empty_context_custody_for_kernel_processes();
|
|
});
|
|
|
|
auto unmount_was_successful = true;
|
|
while (unmount_was_successful) {
|
|
unmount_was_successful = false;
|
|
Vector<Mount&, 16> mounts;
|
|
TRY(vfs_root_context.mounts().with([&mounts](auto& list) -> ErrorOr<void> {
|
|
for (auto& mount : list) {
|
|
TRY(mounts.try_append(const_cast<Mount&>(mount)));
|
|
}
|
|
return {};
|
|
}));
|
|
if (mounts.is_empty())
|
|
break;
|
|
auto const remaining_mounts = mounts.size();
|
|
|
|
while (!mounts.is_empty()) {
|
|
auto& mount = mounts.take_last();
|
|
TRY(mount.guest_fs().flush_writes());
|
|
|
|
auto mount_path = TRY(mount.absolute_path());
|
|
auto& mount_inode = mount.guest();
|
|
auto const result = VirtualFileSystem::the().unmount(vfs_root_context, mount_inode, mount_path->view());
|
|
if (result.is_error()) {
|
|
dbgln("Error during unmount of {}: {}", mount_path, result.error());
|
|
// FIXME: For unknown reasons the root FS stays busy even after everything else has shut down and was unmounted.
|
|
// Until we find the underlying issue, allow an unclean shutdown here.
|
|
if (remaining_mounts <= 1)
|
|
dbgln("BUG! One mount remaining; the root file system may not be unmountable at all. Shutting down anyways.");
|
|
} else {
|
|
unmount_was_successful = true;
|
|
}
|
|
}
|
|
}
|
|
return {};
|
|
}
|
|
|
|
ErrorOr<void> PowerStateSwitchTask::perform_shutdown(PowerStateSwitchTask::DoReboot do_reboot)
|
|
{
|
|
// We assume that by this point userland has tried as much as possible to shut down everything in an orderly fashion.
|
|
// Therefore, we force kill remaining processes, including Kernel processes, except the finalizer and ourselves.
|
|
dbgln("Killing remaining processes...");
|
|
Optional<Process&> finalizer_process;
|
|
Process::all_instances().for_each([&](Process& process) {
|
|
if (process.pid() == g_finalizer->process().pid())
|
|
finalizer_process = process;
|
|
});
|
|
VERIFY(finalizer_process.has_value());
|
|
|
|
// Allow init process and finalizer task to be killed.
|
|
g_in_system_shutdown = true;
|
|
|
|
// Make sure to kill all user processes first, otherwise we might get weird hangups.
|
|
TRY(kill_all_user_processes());
|
|
|
|
size_t alive_process_count = 0;
|
|
Process::all_instances().for_each([&](Process& process) {
|
|
if (!process.is_kernel_process() && !process.is_dead())
|
|
alive_process_count++;
|
|
});
|
|
// Don't panic here (since we may panic in a bit anyways) but report the probable cause of an unclean shutdown.
|
|
if (alive_process_count != 0)
|
|
dbgln("We're not the last process alive; proper shutdown may fail!");
|
|
|
|
VirtualConsole::switch_to_debug_console();
|
|
|
|
dbgln("Locking all file systems...");
|
|
FileSystem::lock_all();
|
|
FileSystem::sync();
|
|
|
|
dbgln("Unmounting all file systems...");
|
|
|
|
size_t collected_contexts_count = 0;
|
|
do {
|
|
Array<RefPtr<VFSRootContext>, 16> contexts;
|
|
VirtualFileSystem::the().all_root_contexts_list(Badge<PowerStateSwitchTask> {}).with([&collected_contexts_count, &contexts](auto& list) {
|
|
size_t iteration_collect_count = min(contexts.size(), list.size_slow());
|
|
for (collected_contexts_count = 0; collected_contexts_count < iteration_collect_count; collected_contexts_count++) {
|
|
contexts[collected_contexts_count] = list.take_first();
|
|
}
|
|
});
|
|
for (size_t index = 0; index < collected_contexts_count; index++) {
|
|
VERIFY(contexts[index]);
|
|
TRY(unmount_mounts_on_vfs_root_context(*contexts[index]));
|
|
}
|
|
} while (collected_contexts_count > 0);
|
|
|
|
// NOTE: We don't really need to kill kernel processes, because in contrast
|
|
// to user processes, kernel processes will simply not make syscalls
|
|
// or do some other unexpected behavior.
|
|
// Therefore, we just lock the scheduler big lock to ensure nothing happens
|
|
// beyond this point forward.
|
|
SpinlockLocker lock(g_scheduler_lock);
|
|
|
|
if (do_reboot == DoReboot::Yes) {
|
|
dbgln("Attempting system reboot...");
|
|
dbgln("attempting reboot via ACPI");
|
|
if (ACPI::is_enabled())
|
|
ACPI::Parser::the()->try_acpi_reboot();
|
|
arch_specific_reboot();
|
|
|
|
dmesgln("Reboot can't be completed. It's safe to turn off the computer!");
|
|
Processor::halt();
|
|
VERIFY_NOT_REACHED();
|
|
}
|
|
VERIFY(do_reboot == DoReboot::No);
|
|
|
|
dbgln("Attempting system shutdown...");
|
|
arch_specific_poweroff();
|
|
dmesgln("Shutdown can't be completed. It's safe to turn off the computer!");
|
|
Processor::halt();
|
|
VERIFY_NOT_REACHED();
|
|
}
|
|
|
|
ErrorOr<void> PowerStateSwitchTask::kill_all_user_processes()
|
|
{
|
|
{
|
|
SpinlockLocker lock(g_scheduler_lock);
|
|
Process::all_instances().for_each([&](Process& process) {
|
|
if (!process.is_kernel_process())
|
|
process.die();
|
|
});
|
|
}
|
|
|
|
// Although we *could* finalize processes ourselves (g_in_system_shutdown allows this),
|
|
// we're nice citizens and let the finalizer task perform final duties before we kill it.
|
|
Scheduler::notify_finalizer();
|
|
int alive_process_count = 1;
|
|
MonotonicTime last_status_time = TimeManagement::the().monotonic_time();
|
|
while (alive_process_count > 0) {
|
|
Scheduler::yield();
|
|
alive_process_count = 0;
|
|
Process::all_instances().for_each([&](Process& process) {
|
|
if (!process.is_kernel_process() && !process.is_dead())
|
|
alive_process_count++;
|
|
});
|
|
|
|
if (TimeManagement::the().monotonic_time() - last_status_time > Duration::from_seconds(2)) {
|
|
last_status_time = TimeManagement::the().monotonic_time();
|
|
dmesgln("Waiting on {} processes to exit...", alive_process_count);
|
|
|
|
if constexpr (PROCESS_DEBUG) {
|
|
Process::all_instances().for_each_const([&](Process const& process) {
|
|
if (!process.is_kernel_process() && !process.is_dead()) {
|
|
dbgln("Process (user) {:2} dead={} dying={} ({})",
|
|
process.pid(), process.is_dead(), process.is_dying(),
|
|
process.name().with([](auto& name) { return name.representable_view(); }));
|
|
}
|
|
});
|
|
}
|
|
}
|
|
}
|
|
|
|
return {};
|
|
}
|
|
|
|
}
|