ladybird/Kernel/Firmware/PowerStateSwitch.cpp
Liav A f8489da8ee Kernel/SysFS: Provide a way to "truncate" and "set" mtime on inodes
Normally, trying to truncate a SysFSInode should result in EPERM error.
However, as suggested by Ali (@alimpfard), we can allow the PowerState
node to be "truncated" so one can open that file with O_TRUNC option.
Likewise, we also need to provide a way to set modified time on SysFS
inodes. For most inodes, we should return ENOTIMPL error, but for the
power state switch, we ignore the modified time setting and just return
KSuccess.

These fixes allow to do "echo -n 1 > /sys/firmware/power_state" in Shell
after gaining root permissions, to switch the power state.
2021-10-09 12:07:56 +02:00

109 lines
3.1 KiB
C++

/*
* Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
* Copyright (c) 2021, Liav A. <liavalb@hotmail.co.il>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <Kernel/Arch/x86/IO.h>
#include <Kernel/FileSystem/FileSystem.h>
#include <Kernel/Firmware/ACPI/Parser.h>
#include <Kernel/Firmware/PowerStateSwitch.h>
#include <Kernel/Process.h>
#include <Kernel/Sections.h>
#include <Kernel/TTY/ConsoleManagement.h>
namespace Kernel {
mode_t PowerStateSwitchNode::permissions() const
{
return S_IRUSR | S_IRGRP | S_IWUSR | S_IWGRP;
}
UNMAP_AFTER_INIT NonnullRefPtr<PowerStateSwitchNode> PowerStateSwitchNode::must_create(FirmwareSysFSDirectory& firmware_directory)
{
return adopt_ref_if_nonnull(new (nothrow) PowerStateSwitchNode(firmware_directory)).release_nonnull();
}
UNMAP_AFTER_INIT PowerStateSwitchNode::PowerStateSwitchNode(FirmwareSysFSDirectory&)
: SysFSComponent("power_state")
{
}
KResult PowerStateSwitchNode::truncate(u64 size)
{
// Note: This node doesn't store any useful data anyway, so we can safely
// truncate this to zero (essentially ignoring the request without failing).
if (size != 0)
return EPERM;
return KSuccess;
}
KResultOr<size_t> PowerStateSwitchNode::write_bytes(off_t offset, size_t count, UserOrKernelBuffer const& data, OpenFileDescription*)
{
if (Checked<off_t>::addition_would_overflow(offset, count))
return EOVERFLOW;
if (offset > 0)
return EINVAL;
if (count > 1)
return EINVAL;
char buf[1];
TRY(data.read(buf, 1));
switch (buf[0]) {
case '0':
return EINVAL;
case '1':
reboot();
VERIFY_NOT_REACHED();
case '2':
poweroff();
VERIFY_NOT_REACHED();
default:
return EINVAL;
}
VERIFY_NOT_REACHED();
}
void PowerStateSwitchNode::reboot()
{
MutexLocker locker(Process::current().big_lock());
dbgln("acquiring FS locks...");
FileSystem::lock_all();
dbgln("syncing mounted filesystems...");
FileSystem::sync();
dbgln("attempting reboot via ACPI");
if (ACPI::is_enabled())
ACPI::Parser::the()->try_acpi_reboot();
dbgln("attempting reboot via KB Controller...");
IO::out8(0x64, 0xFE);
dbgln("reboot attempts failed, applications will stop responding.");
dmesgln("Reboot can't be completed. It's safe to turn off the computer!");
Processor::halt();
}
void PowerStateSwitchNode::poweroff()
{
MutexLocker locker(Process::current().big_lock());
ConsoleManagement::the().switch_to_debug();
dbgln("acquiring FS locks...");
FileSystem::lock_all();
dbgln("syncing mounted filesystems...");
FileSystem::sync();
dbgln("attempting system shutdown...");
// QEMU Shutdown
IO::out16(0x604, 0x2000);
// If we're here, the shutdown failed. Try VirtualBox shutdown.
IO::out16(0x4004, 0x3400);
// VirtualBox shutdown failed. Try Bochs/Old QEMU shutdown.
IO::out16(0xb004, 0x2000);
dbgln("shutdown attempts failed, applications will stop responding.");
dmesgln("Shutdown can't be completed. It's safe to turn off the computer!");
Processor::halt();
}
}